欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 基本语法和数据结构

程序员文章站 2022-04-16 22:13:15
...

一个 Java 的 HelloWorld 程序

public class FirstProgram
{
    public static void main(String[] args)
    {
        System.out.println("Hello World!");
    }
}

编译

使用 javac.exe 对源代码进行编译。

javac 的用法是:

javac -d destdir srcFile

其中使用 -d 选项指定编译后生成的文件的输出位置。如果不指定 -d 选项,那么默认输出到当前路径。

编译结束之后,javac 程序会为源代码中的每一个类生成一个对应的 .class 文件,文件名为相应的类名。也就是说,如果一个源文件中定义了两个类,那么编译这个源文件会生成两个 .class 文件,对应源代码中的两个类。

运行

使用 java.exe 执行类。

用法是:

java 类名

java 后直接输入相应的类名,不带文件名后缀。

使用 java.exe 执行相应的类的时候,要求所执行的类必须有 main 方法,而且 main 方法必须是 public static void main(String[] args) 这样的写法。

关于 CLASSPATH

JDK 1.5 之后的版本可以不用设置 CLASSPATH。JDK 1.5 之后的版本,使用 java 类名 运行程序,java 会默认在当前目录下寻找类文件,但是 JDK 1.5 之前并没有设置这个机制,需要借助于 CLASSPATH 环境变量或者 -classpath 参数才知道去哪里寻找类。所以 JDK 1.5 之后就可以不设置 CLASSPATH 环境变量了,而 JDK 1.5 之前还是需要设置的。

另外,也可以使用 CLASSPATH 来制定一些第三方类库的路径,从而让程序可以找到相应的第三方类库。

命名规则及大小写

  • Java是区分大小写的。对于大小写问题,简单总结如下:
区分大小写的语言 不区分大小写的语言
Java、C/C++、C#、Python 汇编(Assembly)、SQL、Visual Basic

文件名命名规则

一个 .java 文件中至多只能有一个 public 类,Java 源代码文件的文件名必须和源代码中公共类(public类)的名字相同,文件名和公共类的大小写也要保持一致,不然编译时会报错。

标识符命名规则

Java 的标识符必须以字母、下划线(_)、美元符号($)开头,后面可以跟任意数目的字母、数字、下划线和美元符号。此处的字母不仅仅可以是英文字母,还可以是中文字符、日文字符等等 Unicode 字符。ASCII 字符中,除了大小写拉丁字母和下划线和美元符号,其他字符如 @# 不能用于标识符。

Java官方文档中关于标识符的说明:3.8. Identifiers - Chapter 3. Lexical Structure

注释

和 C/C++ 相同,Java支持使用 ///* */ 进行注释。

需要注意的是 /* */ 注释方式不支持嵌套,所以使用时还要必要看具体要注释的代码的情况。如果要注释掉的代码中出现了 /* 或者 */ ,可能会和你添加的注释就近配对,从而达不到你想要的效果。

文档注释

另外还有一种注释方式,这种注释方式以 /** 开头,以 */ 结尾。这种注释形式可以使用 Javadoc 工具自动生成 API 文档。

/**
 * This is a useful way of comments
 * @version 1.00 2018-12-06
 * @author 52Heartz
 */

public class FirstProgram
{
    public static void main(String[] args)
    {
        System.out.println("Hello World!");
    }
}

基本数据类型

Java是一种强类型语言,必须为每一个变量显式地声明一种类型。

Java有8种基本数据类型,称为 Primitive type,其中有4种整型、2种浮点类型、1种用于表示Unicode编码的字符单元的字符类型char类型和一种用于表示布尔值的boolean类型。

整型

类型 占用的存储空间大小 取值范围
int 4 Byte -2 147 483 648 ~ 2 147 483 647(正负21亿多,正负10位数)
short 2 Byte -32 768 ~ 32 768
long 8 Byte -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807(正负19位数,最高位是9)
byte 1 Byte -128 ~ 127

实际应用情况

  • 通常情况下,int 类型最为常用,如果 int 不够用,就使用 long
  • byteshort 类型主要用于特定场合,例如底层的文件处理或者需要控制占用存储空间量的大数组。

关于基本数据类型,Java和C/C++的区别

在C和C++中,int和long等类型的大小与目标平台相关。在8086这样的16位处理器上整形数值占2字节;不过在32位处理器(比如Pentium或SPARC)上,整形数值则为4字节。类似地,在32位处理器上long值为4字节,在64位处理器上则为8字节。由于存在这些差别,这对编写跨平台程序带来了很大难度。在Java中,所有的数值类型所占据的字节数量与平台无关。

注意,Java没有任何无符号(unsigned)形式的int、long、short、或byte类型。

长整型常量

当表示长整型常量的时候,需要在数字后边加上 L 或者 l ,也就是说需要表示大于2 147 483 647或者小于-2 147 483 648的常数的时候需要在数字后边加上 L 或者 l 。如果-2 147 483 648和2 147 483 647之间就可以不在在数字后边加L。

数字加下划线增强可读性

整型常量太长的时候不利于阅读和识别。从Java7开始可以给数字字面量加下划线来增强可读性。

例如 1_000_000 表示一百万,或者使用中国习惯的表示方法,使用 1_0000_0000 表示一亿。

这些下划线只是为了让人更容易读。Java编译器会去除这些下划线。

表示十六进制常量

数字前加上前缀 0x 或者 0X,比如 Ox123A

表示二进制常量

数字前加上前缀 0b 或者 0B,比如 Ob1101 对应于十进制中的15。

表示八进制常量

数字前面加上前缀 0,比如 010 对应于十进制中的8。

浮点类型

类型 存储需求 取值范围
float 4 Byte 大约±3.402 823 47E+38F(有效位数为6-7位)
double 8 Byte 大约±1.797 693 134 862 315 70E+308(有效位数为15位)

实际应用情况

float一般称为单精度浮点型,double称为双精度浮点型。绝大部分应用程序都采用double类型,在很多情况下,float类型的精度很难满足需求。实际上,只有很少的情况适合使用float类型,例如,需要单精度数据的库,或者需要存储大量数据。

表示浮点型常量

表示单精度类型的常量需要在数字末尾加上一个后缀 F 或者 f。例如 3.141592F

表示双精度浮点型的常量需要在数字末尾加上一个后缀 D 或者 d 。例如 3.1415926

如果如果一个小数商量后面什么也不加,那么默认为double类型。

为什么double的有效位数只有15位?

// TODO 结合计数法等相关知识

参考 java中的float和double的精度问题

使用十六进制表示浮点数值

可以使用十六进制表示浮点数值。例如,0.125=230.125=2^{-3} 可以表示成 0x1.0p-3。在十六进制表示法中,使用p表示指数,而不是e。注意,尾数采用十六进制,指数采用十进制。指数的基数是2,而不是10。

溢出和出错情况

  • 正无穷大 Double.POSITIVE_INFINITY。例如一个正整数除以零的结果为正无穷大。
  • 负无穷大 Double.NEGATIVE_INFINITY
  • NaN(不是一个数字) Double.NaN。例如计算 0/0 或者负数的平方根结果为 NaN

需要注意的是整数被0除将会产生一个异常,而浮点数被0除将会得到无穷大或者 NaN

检测一个变量是不是数字的方式

//正确方式,使用isNaN()方法
if (Double.iaNaN(x)){ ... };

//错误方式,直接和Double.Nan比较
if (x == Double.NaN){ ... };
//解释:所有“非数值”的值都被认为是不相同的,所以不能直接和Double.NaN比较

注意浮点数的误差

浮点数值不适用于无法接受舍入误差的金融计算中。例如,命令 System.out.println(2.0-1.1) 将打印出 0.8999999999999999 ,而不是人们想象中的 0.9。这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10。这就好像十进制无法精确地表示分数 1/3 一样。如果在数值计算中不允许有任何舍入误差,就应该使用 BigDecimal 类。

char类型

char类型常量的表示

必须使用单引号括起来的单个字符才是char类型常量。例如 'A'

如果使用双引号括起来单个字符,那么就是包含一个字符的字符串常量,不是char类型常量

转义字符

有一些字符因为已经被语言用来作为语法规则的一部分了,比如双引号 "。还有像换行符这种无法看到控制字符都可以采用转义字符来表示。

可是使用 \n 表示换行符,\t 表示制表符。

使用\"表示 "这个字符,这个时候 " 不再作为字符串的标志。

使用Unicode值表示字符

可以使用 \u0041 表示字符 A\u0008 表示控制字符 退格\u0022 表示字符 "

\u 后面跟的是4个十六进制的整数。

直接使用Unicode表示代码

比如,\u0053表示大写字母 S,那么直接用 \u0053 替换代码中的 S,程序仍然可以成功编译运行。例如:

public class Main {
    public static void main(String[] args) {
        \u0053ystem.out.println("Hello World");
    }
}

使用Unicode表示法时候的注意事项

  • 注意点1

Unicode转义序列会在解析代码之前得到处理。例如"\u0022+\u0022" 并不是一个由引号(U+0022)包围加号构成的字符串。实际上,\u0022会在解析之前转换为 ",这会得到 ""+"",也就是一个空串。

例如

System.out.println("\u0022+\u0022") //输出空串

如果想要达到预期的结果需要这样:

System.out.println("\"+\"") //输出 "+"

或者这样:

System.out.println("\u005c\u0022+\u005c\u0022")
  • 注意点2:小心注释中使用Unicode的地方

比如:

//\u00A0 is a newline

这会产生一个语法错误,因为读程序时 \u00A0 会替换为一个换行符。

还有:

//Look inside c:\users

这也会产生一个语法错误,因为\u后面并未跟着4个十六进制整数。

Unicode和char类型拓展知识

//TODO Java核心技术 卷I 3.3.4

boolean类型

boolean(布尔)类型有两个值:falsetrue,用来判定逻辑条件。整形值和布尔值之间不能进行相互转换。

和C/C++不同的地方

在C/C++中,数值甚至指针可以代替boolean值,值0相当于布尔值false,非零相当于布尔值true。在Java中不是这样。

变量

变量名命名要求

  • 字符选择范围

与大多数程序设计语言相比,Java中“字母”和“数字”的范围更大。字母包括’A’‘Z’、‘a’‘z’、’_’、’$'或者在某种语言中表示字母的任何Unicode字符。

比如,可以使用汉字作为变量名:

public class Main {
    public static void main(String[] args) {
        String 你好 = "你好世界";
        System.out.println(你好);
    }
}
  • 不能选择的字符

+©这样的符号不能出现在变量名中,空格也不能出现在变量名中。

  • 不建议选择的字符

尽管 $ 是一个合法的Java字符,但不要在你自己的代码中使用这个字符。它只用在Java编译器或其他工具生成的名字中。

  • 变量名区分大小写

比如,hireDayhireday 是两个不同的变量名。但是建议在对两个不同的变量进行命名的时候,最好不要只存在大小写上的差异。

变量初始化

声明一个变量之后必须对变量进行显式初始化,使用未初始化的变量会报错。

常量

在Java中,使用关键字 final 指示常量。关键词 final 表示这个变量只能被赋值一次,一旦被赋值之后就不能再更改了。习惯上,常量名使用全大写。

示例:

final double CM_PER_INCH = 2.54;

类常量

Java中可以使用关键字 static final 定义类常量。

类常量只能定义在一个类中所有的方法之外,不能定义在类的某个方法当中。

如果定义类常量时同时把变量声明为一个 public 变量,那么其他类的方法也可以使用这个常量。

示例:

public class Constants2
{
    public static final double CM_PER_INCH = 2.54;
    
    public static void main(String[] args)
    {
        //...
    }
}

这样,别的类就可以通过 Constants2.CM_PER_INCH 使用这个类常量。

另外,关键字 const 在Java中是保留字,但目前并没有使用。

运算符

关于浮点计算

可移植性是Java语言的设计目标之一。无论在哪个虚拟机上运行,同一运算应该得到相同的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double类型使用64位存出一个数值,而有些处理器使用80位浮点寄存器。这些寄存器增加了中间过程的计算精度。

这就对在不同机器上产生相同的计算结果带来的困难。解决方法是使用 strictfp 关键字来对方法或者类进行标记。

//TODO 这里有待进一步解释 参考Java核心技术 卷I 3.5

数学函数与常量

在Math类中,包含了各种各样的数学函数。

  • 求平方根
Math.sqrt() //计算一个数的平方根
  • 求幂
double y = Math.pow(x, a); //将y的值设置为x的a次幂;此方法的两个参数都是double类型,返回结果也是double类型
  • π和e的近似值

Math.PIMath.E

关于结果的 完全可预测性计算速度 之间的取舍

在Math类中,为了达到最快的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,那么就应该使用 Strictmath类。它使用“*发布的Math库”(fdlibm)实现算法,以确保在所有平台上得到相同的结果,有关这些算法的源代码参看www.netlib.org/fdlibm。

数值类型之间的转换

精度较高的类型转换为精度较低的类型时会丢失精度。

不同数值类型相互运算

两个数值类型的变量之间的运算根据以下规则进行:

  • 如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型。
  • 如果两个操作数中有一个是float类型,另一个操作数就会转换为float类型。
  • 如果两个操作数中有一个是long类型,另一个操作数就会转换为long类型。
  • 否则,两个操作数都将被转换为int类型。

强制类型转换

根据上一个小节可以知道,必要的时候,int类型的值会被自动转换为double类型。

有时候也需要把double转换为int,这种转换通过 强制类型转换 实现。

示例:

double x = 9.997;
int nx = (int)x;

这样,nx 的值就变为9,强制类型转换通过截断小数部分将浮点类型转换为整形。

  • 通过 Math.round() 进行舍入运算

示例:

double x = 9.997;
int nx = (int) Math.round(x);

这种方式先使用 Math.round() 对变量 x 进行了舍入运算,但是返回值为 long,所以还需要再进行强制类型转换为 int型。

Math.round() 的参数为 double 时,返回值为 long 类型;参数为 float 时,返回值为 int 类型。

  • 强制类型转换可能出错的情况

如果试图将一个数值从一种类型转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如 (byte) 300 的实际值为44。

  • 不要对 boolean 类型进行强制类型转换

不要在boolean类型与任何数值类型之间进行强制类型转换,这样可以防止发生错误。只有极少数的情况才需要将布尔类型转换为数值类型,这时可以使用条件表达式:

int a = b ? 1 : 0;

自动进行强制类型转换的情况

如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制类型转换。例如,如果x是一个int,则以下语句

x += 3.5;

是合法的,将把x设置为(int)(x+3.5)

三元运算符 condition ? expression1 : expression2

在合适的地方使用 condition ? expression1 : expression2 能让代码简洁清晰。

位运算符(逻辑类型)

位运算符是针对二进制的位进行操作的。

与(and):& - 只有两个位都为1的时候,运算结果才为1,否则为0

或(or):| - 两个位中有1个为1,运算结果就为1,否则为0

异或(xor):^ - 两个位相同则结果为0,不同则为1。

非(not):~ - 按位取非

  • 可以通过掩码技术得到整数中的各个位。
  • 应用在布尔类型上

&| 作用在布尔类型上时,也会得到一个布尔值。这个结果和使用 &&|| 类似。但是区别在于,&&|| 采用“短路”求值的方式,比如若 && 的第一个操作数为false,那么就不会对第二个操作数进行计算了。但是 & 会对两个操作数都进行计算。

位运算符(移位运算)

左移位:<<

右移位:>>

特殊的右移位:>>>

移位运算符的左操作数是被移的数,右操作数是要移动的位数。

num << 1 相当于左移一位,相当于num乘以2。即整体左移移位,低位补0。

num >> 1 相当于右移一位,相当于num除以2。即整体右移一位,符号位不动,和符号位相邻的位补上和符号位一样的数。

num >>> 1 相当于右移一位,和 >> 不同的是,这个运算符会让符号位也一起向右移动。高位补0。

注意:没有 <<< 这个运算符。

注意

移位运算符本身不是原地运算,也就是说,不能写出一条单独的语句 a >> 1;,要写成 a = a >> 1;

或者使用 >>= 或者 >>>= 或者 <<=。也就是写成 a >>= 1; 这样。

枚举类型

枚举类型的变量只能存储这个类型声明中给定的某个枚举值。

示例:

enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE};
Size s = Size.MEDIUM;

循环语法

for 循环

for ( [ForInit] ; [Expression] ; [ForUpdate] ) Statement

for语句的第一句是初始化语句,但是这个初始化语句只能放一个初始化语句,这就意味着初始化语句只能定义一种类型的变量。比如 for(int a = 0, b = 1;;) 这样是正确的。但是 for(int a = 0, double b = 2.0;;) 就是错误的。

关于 for 语句的初始化 14.14.1.1. Initialization of for Statement

那么如果想要在 for 语句中初始化两个不同类型的变量怎么办呢?

可以考虑把 for 语句放在一个代码块中,也就是这样:

{
    int a = 0, b = 1;
    double c = 3.0;
    for (; a < 10 && b < 10; ++a, ++b) {
        System.out.println("a:" + a);
        System.out.println("b:" + b);
    }
}

这样,变量 a 、变量 b 和变量 c 的作用范围都只在代码块中。