代码示例
现有代码示例如下,其中通过匿名内部类的方式创建线程,并且内部类中需要的参数通过final被定义成常量,其中需要注意的是 在jdk1.7需要显式声明final关键字,否则语法报错,而jdk1.8会隐性加上final
public class InnerFinalDemo {
public static int addByThreads(final List list) {
// 创建一个线程组
ThreadGroup group = new ThreadGroup("Group");
// 通过内部类的方法来创建多线程
Runnable listAddTool = new Runnable() {
public void run() {// 在其中定义线程的主体代码
//list = new ArrayList();会报错 因为已经定义为final
list.add("0"); // 在集合里添加元素
}
};
// 启动10个线程,同时向集合里添加元素
for (int i = 0; i < 10; i++) {
new Thread(group, listAddTool).start();
}
while (group.activeCount() > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return list.size(); // 返回插入后的集合长度
}
public static void main(String[] args) {
List list = new ArrayList();
//很大可能返回10
System.out.println(addByThreads(list));
}
}
原因分析
- 内部类和外部类是平行的,没有隶属关系,也就是说内部类和外部类的执行顺序不定
- 外部类有可能会先于内部类回收,如果不加final,参数会被回收,从而导致内部类没法使用
- 加final后,会以常量的方式存储,而不是存在堆里,所以外部类回收后,该参数照样会存在
额外分析:
可以看到这里的final 和之前的final相比作用比较独特,它与垃圾回收机制相关,,定义成final常量的形参它在运行时存储于方法区中的运行时常池中,从而防止被回收掉
另外本人最近所见与此相关的面试题如下:
问:内部类访问局部变量的时候,为什么变量必须加上 final 修饰?
答:因为生命周期不同。局部变量在方法结束后就会被销毁,但内部类对象并不一定,这样就会导致内部类引用了一个不存在的变量。所以编译器会在内部类中生成一个局部变量的拷贝,这个拷贝的生命周期和内部类对象相同,就不会出现上述问题。但这样就导致了其中一个变量被修改,两个变量值可能不同的问题。为了解决这个问题,编译器就要求局部变量需要被 final 修饰,以保证两个变量值相同。
另外参考了另一篇文章的内容可以用来究其原因 如下:
内部类编译成功后会单独产生一个class文件 与外部类不是同一个,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用:
public class OuterClass {
public void display(final String name,String age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
从上面代码中看好像name参数应该是被内部类直接调用?其实不然,在java编译之后实际的操作如下:
public class OuterClass$InnerClass {
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}
public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}
所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。
在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。
简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。
故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。