【面试对宝典40题的解释】为什么从内部类中访问局部变量需要被声明为final最终类型

来源:互联网 发布:淘宝怎么弄金牌卖家 编辑:程序博客网 时间:2024/05/22 06:55
在搞Spring时,有很多方法都用到了回调函数,因而涉及到了更多的内部类,在使用内部类时,发现从内部类中访问局部变量需要被声明为最终类型(final),有些迷惑,找了一些分析文章。如下:

这是从编译器的角度去分析的:
class A{    public void shout(final   int iargs){        class B{            public void shout1(){                System.out.println(iargs);            }        }        B b=new B();        b.shout1();    }}class C{    public static void main(String [] args){        A a=new A();        a.shout(5);    }}
这段代码...为什么要加final我觉得这个外部类的方法直接把参数传进去...执行方法...产生一个B对象调用这个shout1();就可以了.为什么非要加final?我知道一个方法中局部变量使用完之后就被释放掉了.而final定义的就超过了这个外部方法中的生命周期...但是就是搞不懂啊..能自己讲讲吗?
------------------解释如下------------------

局部内部类直接访问在其外部定义的对象(包括普通变量),编译器要求参数引用必须是final的。

其中:
1. 必须是局部内部类,显然包括匿名内部类;
2. 内部类访问外部类的对象必须是直接访问。
看下面的代码,注意a并不需要是final的:
类A1:
class A1{    public void shout(int iargs){       class B{         public void shout1(int a){          System.out.println(a);         }        }       B b = new B();       b.shout1(iargs);   }}
类A:
class A{    public static void main(String [] args) {        A1 a = new A1();        a.shout(5);    }}
再看这个代码:
class A1{   public void shout1(){     System.out.println("hi");   }    public A1 shout(final int iargs){    class B extends A1{     public void shout1(){       System.out.println(iargs);     }        }    return new B();   }}
类A:
class A{   public static void main(String [] args){    A1 a = new A1();    C c = new C();    c.shoutc(a.shout(5));   }}
类C:
class C{   void shoutc(A1 b){     b.shout1();   }}

语句 c.shoutc(a.shout(5)); 在 a.shout(5) 得到返回值后,a 的 shout()方法栈被清空了!
即iargs不存在了,而c.shoutc()却又调用了 b.shout1(); 语句去执行 System.out.println(iargs); 你不觉得很诡异吗?呵呵。

我们再来看java虚拟机是怎么实现这个诡异的访问的:有人认为这种访问时之所以能完成是因为iargs是final的,由于变量的生命周期,事实是这样的吗?方法栈都不存在了,变量即使存在,怎么可能能访问到?试想下:一个方法能访问另一个方法的定义的final局部变量吗(不通过返回值)?

研究一下这个诡异的访问执行的原理,用反射探测一下局部内部类
编译器会探测局部内部类中是否有直接使用外部定义变量的情况:如果有访问就会定义一个同类型的字段然后在构造方法中用外部变量给自己定义的字段赋值而后局部内部类所使用的变量都是自己定义的字段!当然就可以访问!见下:
class A1$1$B{    A1$1$B(A1, int);    private final int var$iargs;    private final A1 this$0;}

A1$1$B 类型的对象会使用var$iars变量,而不是shout()方法中的final int iargs变量,当然就可以访问了!

OK,终于到正题了,为什么是final,即使外部变量不是final,编译器也可以如此处理:自己定义一个同类型的变量,然后在构造方法中赋值就行了。

原因就是为了让我们能够挺合逻辑的直接使用外部变量,而且看起来是在始终使用 iargs 变量(而不是赋值以后的自己的字段)!

考虑出现这种情况:局部内部类使用的是外部的变量iargs,而且又对这个变量作了变值操作,如:iargs++,根据前面的分析,如果编译器允许iargs不是final的,那么,改变的是 var$iargs,而iargs并没有变!仍然是5(var$iargs才是6)。为了避免这样如此不合逻辑的事情发生:你用了外部变量,又改变了变量的值,但那个变量死活没有变化!自然的iargs被强行规定必须是final 所修饰的(让两个值永远一样,或所指向的对象永远一样,后者可能更重要)!

0 0