public class Test {
public static void main(String args[]) {
System.out.println(new WithFinalFields());
System.out.println(new WithFinalFields());
}
}
class SelfCounter {
private static int counter;
private int id = counter++;
public String toString() {
return "SelfCounter: " + id;
}
}
class WithFinalFields {
static final SelfCounter wffs = new SelfCounter();
final SelfCounter wff = new SelfCounter();
public String toString(){
return "wff = "+wff+"\n wffs ="+wffs;
}
}
首先给出一段代码,其中有两个类,一个类对象作为另一个类的成员变量。要求给出程序的结果,因为对于静态变量的理解不够深刻,并不知道这个程序的结果是什么,干脆找到结果对其分析一番。
wff = SelfCounter: 1
wffs =SelfCounter: 0
wff = SelfCounter: 2
wffs =SelfCounter: 0
我们发现主函数中定义了两个变量,但是两个变量的toString执行的结果并不一样,我们具体分析下。
首先创建第一个变量的时候,我们需要先加载WithFinalFields这个类,加载类的时候会初始化所有的static部分,包括常量,变量,还有static代码块。这个类中只有wffs为静态变量,那么我们需要先加载SelfCounter类,发现其中存在counter这个静态变量,没有显式赋值,那么根据java的特性,SelfCounter类中的counter的值就为0;这时实例化SelfCounter类,id为0,并且counter变为1。实例化结束,wffs的id为0,这是第一个结果。加载WithFinalFields的过程结束,开始实例化WithFinalFields,这时我们只需要初始化wff变量,因为wffs是静态变量,它已经在类加载的时候加载过了。实例化wff变量时,SelfCounter类已经被加载,我们只需要实例化wff变量就行,这时SelfCounter类中的counter为1,那么wff.id==1,SelfCounter类中counter变为2.第一个WithFinalFields变量实例化结束。
这时我们开始实例化第二个WithFinalFields变量,我们的两个类都已经被加载,无需再次加载。我们首先实例化的是wff变量,因为wffs是静态变量,无需被实例化,wffs的id仍然为0。我们继续实例化wff变量,wff.id==2,SelfCounter类中counter变为3。至此第二个WithFinalFields变量实例化结束,结果也明了了。
在这个程序里面,我并没有发现final的作用,主要作用的是static这个修饰。
在我第一次试着理解程序的时候,并不能理解为什么第二个WithFinalFields变量的id仍然为0,后来发现WithFinalFields类中的wffs为静态变量,它的只有一个,只占据一块内存,所以无论创建几个WithFinalFields变量,wffs只有一个,并且它的id是不会发生变化的,因为这个变量初始化只在WithFinalFields类被加载的时候,这个过程只有一次,wffs的id发生被初始化也只有一次。所以它的id是不会发生改变的。
final修饰的变量理论上是不可变的,wff的值理论上是不能被改变的,但它区别于wffs的地方在于它是被初始化两次的,不同的WithFinalFields变量中的wff是不同的变量,占据不同的内存地址,所以虽然不可变,但是对于两个不同变量,就不存在不可变的问题了。
这个问题的讨论源于论坛 [final与static final的区别] (http://bbs.csdn.net/topics/350037073),我的理解完全来自于这个帖子的回复,尤其是四楼的见解,只是整合了一番,作为学习笔记。