kniost

谁怕,一蓑烟雨任平生

0%

Java语言中不可忽视的细节

学习Java时,总会有一些问题需要弄得非常清楚明白,才不至于在写程序的时候犯下各类小错误,这就是本文存在的意义。本文着重于语法细节的查漏补缺,所以不会太全面。在已经能够日常使用Java的情况下参考此文可能更有意义。本文大部分内容来自于Java名著《Thinking in Java》,并将根据看书的进度不断更新。

1. 基本类型

  • Java中每种基本类型所占的存储空间大小是固定的。如下表:
基本类型 大小 最小值 最大值 包装类型
boolean - - - Boolean
char 16 bits Unicode 0 Unicode 2^16 - 1 Character
byte 8 bits - 128 + 127 Byte
short 16 bits - 2^15 + 2^15 - 1 Short
int 32 bits - 2^31 + 2^31 - 1 Integer
long 64 bits - 2^64 + 2^64 - 1 Long
float 32 bits IEEE754 IEEE754 Float
double 64 bits IEEE754 IEEE754 Double
void - - - Void

“-“ 代表语言中没有明确指定,编译器自行决定

  • 基本类型存储在堆栈(Stack)中,同一个值拥有同一个引用,而对象存储在堆(Heap)中
  • 高精度计算使用BitInteger类和BigDecimal
  • 直接定义常量时,加入后缀字符表示类型,使用Ll表示long(尽量不要使用l,因为看起来像1);使用F或者f表示float,使用D或者d表示double
  • 在直接写整数时,加入前缀表示进制不同,整数默认为十进制,加上前缀0x或者0X表示16进制(0F),加上前缀0表示八进制(07)
  • IntegerLongtoBinaryString()方法将整数转为2进制

2. 成员默认值

一个变量被声明时没有初始化,则具有默认值。

  • 类的成员若是基本类型,则默认值如下表,若是对象,则默认为null
基本类型 默认值
boolean false
char ‘\u0000’(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d

需要注意的是局部变量不适用于该默认值,得到的是随机的值,然而在编译过程就不能通过,这也是Java较好的一点(毕竟程序员们不是都自律:)

3. 注释和文档(javadoc)

详细见:How to Write Doc Comments for the Javadoc Tool

  • javadoc 只能为publicprotected成员进行文档注释

4. 按值传递还是按引用传递

按值传递大概指的是像C/C++这类,在传递给函数参数时复制一份,而不对传入的对象进行修改的函数调用方法。
而在Java中,对象都是指向某个内容的引用,多个对象可以同时引用同一个内容,而没有被引用的内容则会被回收。在传递参数并对参数进行修改时,修改的就是对象指向的引用而非复制一份。

5. 操作符

1. 算术操作符

  • 一元加号+将把较小类型的操作数提升为int
  • 和C/C++中一样,++/--在操作数前,表示先运算再使用,而在操作数后时,则是先使用当前的值,再进行运算

2. 关系操作符

  • ==!=比较的是对象的引用,对于基本类型,由于相同的值拥有相同的引用,所以可以直接比较,而对象之间则不能。某些类重写了equals()方法可以用于比较对象的内容,比如String

3. 逻辑操作符

  • &&||!这三个运算符只能用于boolean及其包装类

  • 短路 使用逻辑操作符时,在按照顺序进行运算时,一旦能够明确整个表达式的值,就不再计算表达式余下的部分了。

    如表达式true && false && true && true,在运算到 false 的时候就不会继续运算了,比如有一个String变量str判空时,直接使用条件str != null && str.isEmpty()即可。

6. breakcontinue的标签用法

如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 标签只能加在外侧迭代上方
label1:
// 本示例使用两个迭代,外部迭代和内部迭代
for(i = 0; i < 100; i++) {
for(j = 0; j < 100; j++) {
// 以下四个操作符并非同时存在
// 使用`continue`继续执行内部迭代
continue;
// 使用`break`跳过所有本次内部迭代
break;
// 直接从label1处继续
continue label1;
// 中断并跳出label1所指的迭代
break label1;
}
}

7. 重载(Override)

为了达到相同的目的,使用同样的方法名,就要使用重载。重载的方法用参数列表的参数数目、参数类型和参数顺序区分(尽量不要使用顺序区分)。

8. 初始化

  • 在类的内部,变量定义的先后顺序决定了初始化的先后顺序,即使变量定义在方法之间,它们仍然会在任何方法(包括构造器)被调用之前得到初始化。
  • 静态初始化操作只在首次生成该类的对象时,或者首次访问属于该类的静态数据成员时才会被执行一次。
  • Java中也有实例初始化语句,用法是使用一个大括号将语句包围,它会在构造器之前调用,这种语法对于匿名内部类的初始化是必须的。
  • 数组在初始化时,使用花括号初始化时,最后有一个逗号是合法的,比如 int [] i = {1, 2, 3,};

9. 访问权限控制

  • 访问权限控制等级,从最大权限到最小以此为:publicprotected、包访问权限(没有关键词)和private

10. final关键词

  • 修饰基本类型时,表示其值不可变
  • 修饰对象引用时,表示指向的对象不可变,即不能再指向其他对象,但是对象的内容是可变的
  • 允许不初始化的finalblank final),但必须在使用前被初始化。
  • 修饰方法时,表示不可被子类所覆盖(用final作为内嵌调用的做法已经过时了)
  • 类中所有private方法都隐式指定为final,即不可继承(当然,在子类中,声明一个同样的方法是可以的,但并非是继承的,而是全新的方法)
  • 修饰类时,表示禁止该类被继承,一个final类中所有方法都被隐式指定为final

11. 多态(运行时绑定/后期绑定)

  • Java中除了staticfinal方法(private也是final的),其他所有方法都是后期动态绑定的。也就是说,使用基类的方法时,只要子类覆盖了该方法,就会调用子类的方法,因为编译器知道了其具体类型。
  • 初始化一个对象时,首先将分配给对象的存储空间都初始化为二进制的0,然后先构造根基类,接着依次构造到基类,再按照顺序构造成员,最后调用构造方法。
  • 编写构造器时,应当使用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法