关于final局部变量引用的研究

嵌套类(内部类)方法安全引用外部方法局部变量的原理

嵌套类方法引用外部局部变量,必需将声明为final,否则将出现 Cannot refer to a non-final variable * inside an inner class defined in a different method 编译错误,错误的直接原因是嵌套类对象生命周期与外部方法局部变量生命周期不一致,当外部方法执行完毕,局部变量自动回收,而方法执行产生的新对象不一定会被GC回收(当该对象已被外部对象变量引用时),该对象存续期间,因调用自身方法而引用到已被回收的局部变量,会导致空指针BUG。

 

给个简单示例:

 

 1     public static void main(String[] args) {
 2         ArrayList<Father> list = new ArrayList<>();
 3         for(int i = 0;i<10;i++){
 4             list.add(showA(i));
 5         }
 6         for(int i = 0;i<10;i++){
 7             list.get(i).show();
 8         }
 9     }
10     
11     static abstract class Father{
12         abstract void show();
13     }
14     
15     static Father showA(final int num){
16         class A extends Father{
17             void show(){
18                 System.out.println(num);
19             }
20         }
21         A a = new A();
22         a.show();
23         return a;
24     }

  

代码执行两个循环,第一个循环使用方法showA创建对象,打印传入变量final int num,并将对象保存在外部列表list中,第二个循环遍历list,调取对象并执行对象内打印方法再次打印之前的变量值。

 

当进行第二个for循环时,不像在第一个for循环中在showA方法里执行,此时局部变量num自然不存在(在第一次循环结束后,showA方法执行完毕自动回收了,局部变量num与对象a生命周期不一致!),假设编译器允许showA直接传入int num不声明为final,则第二个for循环中调用a.show()必然触发Null Pointer BUG,但声明为final后,仍然能够打印之前被传入的final int num变量值。

 

通过设置断点调试可以发现,被声明为final的局部变量在被内部类方法引用时,内部类对象会自动在自身内部设入private final字段保存该变量值(如下图示,事实上是在内部类实例化时通过内部默认构造器完成),以延续该变量值的生命周期,以后对象使用该变量不再受外部局部变量生命周期干扰。

 

关于声明为 final 类型的必要性

至此,内部类对外部方法局部变量引用原理已经阐明,但仍未解释说明为何必需要将此局部变量声明为final类型。刚刚关注到网上的博文,恍然大悟了一下,借用其思想转述一下:

因为上文提及到,内部类对外部方法局部变量引用进行自动转存的“编译器设计问题”,并没有显式告诉程序员:外部方法中的局部变量与内部类方法引用的变量存在事实上的不同(这句话很拗口..)。如果在一方改变该变量值,将没有任何额外代码保证两边变量的同步性,表现出来的现象就是,内部类初始化后,再次修改局部变量,内部类方法打印出的变量值出现与实际外部方法局部变量值不一致的莫名其妙的错误。因此,java在编译前就直接强制要求,引用的局部变量必需声明为final类型,不允许进行二次修改。

posted @ 2013-11-27 11:46  wavky  阅读(668)  评论(0编辑  收藏  举报