今天上午看到了一段代码,感觉挺有意思的,就试着敲了一下,看了下输出结果,有点出人意料。
代码是这样的:
package com.marsyoung.test;
import java.util.ArrayList;
import java.util.List;
public class Test {
private static List<Test> objs=new ArrayList<Test>();
static {
objs.add(new Test(Test.S_NAME,Test.NAME,Test.COUNT));
objs.add(new Test(Test.S_NAME,Test.NAME,Test.COUNT));
}
private final static String S_NAME="aaa";
private final static String NAME=new String("aaa");
private final static Long COUNT=1l;
private String name;
private String title;
private Long count;
public Test(String name,String title,Long count){
this.name=name;
this.title=title;
this.count=count;
}
public static void main(String[] args) {
System.out.println(objs);
}
@Override
public String toString() {
return "Test [name="+name+",title="+title+",count="+count+"]";
}
}
输出结果为:
首先理解这样的结果,得先理解代码。
我们可以看出Test类中有一个静态化的List<Test>,同时这个List中通过静态代码块放了两个新new的Test。
虽然存在一个循环引用,但是对于静态的变量,代码只会执行一次,所以这里不会有内存溢出。
接着思考,按照静态代码块和静态变量的加载顺序是正序的,那么在静态代码块加载的时候,S_NAME,NAME,COUNT中应该没有值,所以最后的输出结果中,Test的name,title,count应该是null。
那么为什么最终结果name会等于aaa?
我猜想是由于aaa的传参类型有别于另外两个,在调用new的时候使用的是构造方法,需要传参,"string"进行的是值传递,而Long和new String是引用传递,所以前者可以传到后者传不到。
但是这种想法并不对,因为在静态代码块调用的时候,静态变量还没有被加载到,传参的时候也不可能获取到。
为了证明这点,做个试验,如果我们在上面的代码中,把final去掉之后,则输出结果是:
可以看出关键是final字段,那么final到底做了什么?
查看Test.class文件的反编译代码:
可以看到在静态代码块中,对于被final修饰的S_NAME变量直接就被编译成了aaa放到了new Test()的参数中。而NAME和COUNT还是引用。
为什么final修饰的S_NAME在编译时会直接传个值?这样不会很混乱么?如果也传引用不行么?
我猜想这样做可能和string最终要存储在常量池中有关,这么编译能加快最终class文件的加载和执行速度。。