Java 基本语法和数据结构
基础知识。
一个 Java 的 HelloWorld 程序
1 | public class FirstProgram |
编译
使用 javac.exe
对源代码进行编译。
javac
的用法是:
1 | javac -d destdir srcFile |
其中使用 -d
选项指定编译后生成的文件的输出位置。如果不指定 -d
选项,那么默认输出到当前路径。
编译结束之后,javac
程序会为源代码中的每一个类生成一个对应的 .class
文件,文件名为相应的类名。也就是说,如果一个源文件中定义了两个类,那么编译这个源文件会生成两个 .class
文件,对应源代码中的两个类。
运行
使用 java.exe
执行类。
用法是:
1 | 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 文档。
1 | /** |
基本数据类型
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
。 byte
和short
类型主要用于特定场合,例如底层的文件处理或者需要控制占用存储空间量的大数组。
关于基本数据类型,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 结合计数法等相关知识
使用十六进制表示浮点数值
可以使用十六进制表示浮点数值。例如,$0.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
。
检测一个变量是不是数字的方式
1 | //正确方式,使用isNaN()方法 |
注意浮点数的误差
浮点数值不适用于无法接受舍入误差的金融计算中。例如,命令 System.out.println(2.0-1.1)
将打印出 0.8999999999999999
,而不是人们想象中的 0.9
。这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10
。这就好像十进制无法精确地表示分数 1/3
一样。如果在数值计算中不允许有任何舍入误差,就应该使用 BigDecimal
类。
java浮点类型float和double的主要区别,它们的小数精度范围大小是多少? - 知乎
“==”比较浮点数的时候究竟是怎么比较的我知道很多语言是根据IEEE754标准(如C或Java)? - 知乎
char类型
char类型常量的表示
必须使用单引号括起来的单个字符才是char类型常量。例如 'A'
。
如果使用双引号括起来单个字符,那么就是包含一个字符的字符串常量,不是char类型常量
转义字符
有一些字符因为已经被语言用来作为语法规则的一部分了,比如双引号 "
。还有像换行符这种无法看到控制字符都可以采用转义字符来表示。
可是使用 \n
表示换行符,\t
表示制表符。
使用\"
表示 "
这个字符,这个时候 "
不再作为字符串的标志。
使用Unicode值表示字符
可以使用 \u0041
表示字符 A
,\u0008
表示控制字符 退格
。\u0022
表示字符 "
。
\u
后面跟的是4个十六进制的整数。
直接使用Unicode表示代码
比如,\u0053
表示大写字母 S
,那么直接用 \u0053
替换代码中的 S
,程序仍然可以成功编译运行。例如:
1 | public class Main { |
使用Unicode表示法时候的注意事项
- 注意点1
Unicode转义序列会在解析代码之前得到处理。例如"\u0022+\u0022"
并不是一个由引号(U+0022)包围加号构成的字符串。实际上,\u0022会在解析之前转换为 "
,这会得到 ""+""
,也就是一个空串。
例如
1 | System.out.println("\u0022+\u0022") //输出空串 |
如果想要达到预期的结果需要这样:
1 | System.out.println("\"+\"") //输出 "+" |
或者这样:
1 | System.out.println("\u005c\u0022+\u005c\u0022") |
- 注意点2:小心注释中使用Unicode的地方
比如:
1 | //\u00A0 is a newline |
这会产生一个语法错误,因为读程序时 \u00A0
会替换为一个换行符。
还有:
1 | //Look inside c:\users |
这也会产生一个语法错误,因为\u后面并未跟着4个十六进制整数。
Unicode和char类型拓展知识
//TODO Java核心技术 卷I 3.3.4
boolean类型
boolean(布尔)类型有两个值:false
和 true
,用来判定逻辑条件。整形值和布尔值之间不能进行相互转换。
和C/C++不同的地方
在C/C++中,数值甚至指针可以代替boolean值,值0相当于布尔值false,非零相当于布尔值true。在Java中不是这样。
变量
变量名命名要求
- 字符选择范围
与大多数程序设计语言相比,Java中“字母”和“数字”的范围更大。字母包括’A’‘Z’、’a’‘z’、’_’、’$’或者在某种语言中表示字母的任何Unicode字符。
比如,可以使用汉字作为变量名:
1 | public class Main { |
- 不能选择的字符
+
和 ©
这样的符号不能出现在变量名中,空格也不能出现在变量名中。
- 不建议选择的字符
尽管 $
是一个合法的Java字符,但不要在你自己的代码中使用这个字符。它只用在Java编译器或其他工具生成的名字中。
- 变量名区分大小写
比如,hireDay
和 hireday
是两个不同的变量名。但是建议在对两个不同的变量进行命名的时候,最好不要只存在大小写上的差异。
变量初始化
声明一个变量之后必须对变量进行显式初始化,使用未初始化的变量会报错。
常量
在Java中,使用关键字 final
指示常量。关键词 final
表示这个变量只能被赋值一次,一旦被赋值之后就不能再更改了。习惯上,常量名使用全大写。
示例:
1 | final double CM_PER_INCH = 2.54; |
类常量
Java中可以使用关键字 static final
定义类常量。
类常量只能定义在一个类中所有的方法之外,不能定义在类的某个方法当中。
如果定义类常量时同时把变量声明为一个 public
变量,那么其他类的方法也可以使用这个常量。
示例:
1 | public class Constants2 |
这样,别的类就可以通过 Constants2.CM_PER_INCH
使用这个类常量。
另外,关键字 const
在Java中是保留字,但目前并没有使用。
运算符
关于浮点计算
可移植性是Java语言的设计目标之一。无论在哪个虚拟机上运行,同一运算应该得到相同的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double类型使用64位存出一个数值,而有些处理器使用80位浮点寄存器。这些寄存器增加了中间过程的计算精度。
这就对在不同机器上产生相同的计算结果带来的困难。解决方法是使用 strictfp
关键字来对方法或者类进行标记。
//TODO 这里有待进一步解释 参考Java核心技术 卷I 3.5
数学函数与常量
在Math类中,包含了各种各样的数学函数。
- 求平方根
1 | Math.sqrt() //计算一个数的平方根 |
- 求幂
1 | double y = Math.pow(x, a); //将y的值设置为x的a次幂;此方法的两个参数都是double类型,返回结果也是double类型 |
- π和e的近似值
Math.PI
和 Math.E
关于结果的 完全可预测性
和 计算速度
之间的取舍
在Math类中,为了达到最快的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,那么就应该使用 Strictmath
类。它使用“自由发布的Math库”(fdlibm)实现算法,以确保在所有平台上得到相同的结果,有关这些算法的源代码参看www.netlib.org/fdlibm。
数值类型之间的转换
精度较高的类型转换为精度较低的类型时会丢失精度。
不同数值类型相互运算
两个数值类型的变量之间的运算根据以下规则进行:
- 如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型。
- 如果两个操作数中有一个是float类型,另一个操作数就会转换为float类型。
- 如果两个操作数中有一个是long类型,另一个操作数就会转换为long类型。
- 否则,两个操作数都将被转换为int类型。
强制类型转换
根据上一个小节可以知道,必要的时候,int类型的值会被自动转换为double类型。
有时候也需要把double转换为int,这种转换通过 强制类型转换
实现。
示例:
1 | double x = 9.997; |
这样,nx
的值就变为9,强制类型转换通过截断小数部分将浮点类型转换为整形。
- 通过
Math.round()
进行舍入运算
示例:
1 | double x = 9.997; |
这种方式先使用 Math.round()
对变量 x
进行了舍入运算,但是返回值为 long
,所以还需要再进行强制类型转换为 int
型。
Math.round()
的参数为 double
时,返回值为 long
类型;参数为 float
时,返回值为 int
类型。
- 强制类型转换可能出错的情况
如果试图将一个数值从一种类型转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如 (byte) 300
的实际值为44。
- 不要对
boolean
类型进行强制类型转换
不要在boolean类型与任何数值类型之间进行强制类型转换,这样可以防止发生错误。只有极少数的情况才需要将布尔类型转换为数值类型,这时可以使用条件表达式:
1 | 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;
这样。
枚举类型
枚举类型的变量只能存储这个类型声明中给定的某个枚举值。
示例:
1 | enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE}; |
循环语法
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 语句放在一个代码块中,也就是这样:
1 | { |
这样,变量 a 、变量 b 和变量 c 的作用范围都只在代码块中。