java编程思想(七): 复用类

1. final 关键字

final关键字主要的含义就是“无法改变”。final可以用于数据、方法和类。

1. 数据

final作用于数据上表示数据本身不可以改变。

1
2
3
4
5
6
7
8
final int a = 10;
a = 11; // 错误

static final int C = 2; // 编译期常量
final List<String> list = new ArrayList<String>();
list.add("a"); // 正确
list.add("b"); // 正确
list = new ArrayList<String>(); // 错误,list不能指向其他对象

如上,final作用于引用表示引用值本身不能变,即不能指向其他对象。

final修饰参数

在方法体的匿名内部类中,参数需要用final修饰。

1
2
3
4
5
6
7
8
9
10
11
interface adder{void addXYZ();}
public class outer{
public adder getAdder(final int x){
final int y = 10;
return new adder(){
int z = 10;
@Override
void addXYZ(){return x + y + z;}
}
}
}

因为方法体中的变量是分配在栈上的,生命周期只在方法体内,而内部类对象分配在堆上。如果内部类如果保存外部类的引用,会导致错误。所以为了解决这个问题,java内部会拷贝外部变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface adder{void addXYZ();}
public class outer{
public adder getAdder(final int x){
final int y = 10;
return new adder(){
int copyx = x; // java隐式拷贝
int copyy = y; // java隐式拷贝
int z = 10;
@Override
void addXYZ(){return copyx + copyy + z;}
}
}
}

为了让内外含义保持一致,方法体内的局部变量x,y需要用final修饰。

引申:为什么java不能实现和其他语言一样的函数闭包呢?

2. 方法

final作用于方法有两个方面。

  1. 不能在继承类中重写覆盖
  2. 内联调用

内联调用是为了减少函数调用的开销(压栈、跳转、出栈等),在调用的地方直接用函数代码代替。不过当方法很大时,程序代码会膨胀,性能没有提高。

现在很多虚拟机会检测这种情况,进行优化。所以final主要是为了禁止继承类覆盖。

3. 类

final 类不能被继承。

2. 初始化和类的加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Animal {
{
System.out.println("Animal: 非静态初始化");

}
static {
System.out.println("Animal: 静态初始化");
}
Animal() {
System.out.println("Animal: Constructor");
}
}

class Dog extends Animal {
{
System.out.println("Dog: 非静态初始化");

}
static {
System.out.println("Dog: 静态初始化");
}
Dog() {
System.out.println("Dog: Constructor");
}
}

public class DogConstructor {
public static void main(String[] args) {
new Dog();
}
}/* output:
Animal: 静态初始化
Dog: 静态初始化
Animal: 非静态初始化
Animal: Constructor
Dog: 非静态初始化
Dog: Constructor
*/

如上例所示,步骤如下:

  1. new Dog()触发加载Dog类时,发现继承了基类Animal,于是继续加载基类Animal
  2. 基类静态初始化,进而子类Dog静态初始化
  3. 在堆上分配内存,清空为0(默认初始化)
  4. 基类执行定义初始化和构造器
  5. 子类执行定义初始化和构造器