【面试对宝典40题的解释】为什么从内部类中访问局部变量需要被声明为final最终类型
来源:互联网 发布:淘宝怎么弄金牌卖家 编辑:程序博客网 时间:2024/05/22 06:55
在搞Spring时,有很多方法都用到了回调函数,因而涉及到了更多的内部类,在使用内部类时,发现从内部类中访问局部变量需要被声明为最终类型(final),有些迷惑,找了一些分析文章。如下:
这是从编译器的角度去分析的:
------------------解释如下------------------
局部内部类直接访问在其外部定义的对象(包括普通变量),编译器要求参数引用必须是final的。
其中:
1. 必须是局部内部类,显然包括匿名内部类;
2. 内部类访问外部类的对象必须是直接访问。
看下面的代码,注意a并不需要是final的:
类A1:
语句 c.shoutc(a.shout(5)); 在 a.shout(5) 得到返回值后,a 的 shout()方法栈被清空了!
即iargs不存在了,而c.shoutc()却又调用了 b.shout1(); 语句去执行 System.out.println(iargs); 你不觉得很诡异吗?呵呵。
我们再来看java虚拟机是怎么实现这个诡异的访问的:有人认为这种访问时之所以能完成是因为iargs是final的,由于变量的生命周期,事实是这样的吗?方法栈都不存在了,变量即使存在,怎么可能能访问到?试想下:一个方法能访问另一个方法的定义的final局部变量吗(不通过返回值)?
研究一下这个诡异的访问执行的原理,用反射探测一下局部内部类
编译器会探测局部内部类中是否有直接使用外部定义变量的情况:如果有访问就会定义一个同类型的字段然后在构造方法中用外部变量给自己定义的字段赋值而后局部内部类所使用的变量都是自己定义的字段!当然就可以访问!见下:
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 所修饰的(让两个值永远一样,或所指向的对象永远一样,后者可能更重要)!
这是从编译器的角度去分析的:
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
- 【面试对宝典40题的解释】为什么从内部类中访问局部变量需要被声明为final最终类型
- 【面试对宝典40题的解释】匿名内部类访问外部类中的局部变量必须是final属性
- 为什么局部内部类访问局部变量,局部变量必须声明为final?
- 为什么局部内部类只能访问方法中final类型的局部变量?
- 为什么匿名内部类访问当前方法的局部变量必须为final类型
- 为什么内部类访问局部变量需要加final修饰?
- 为什么java内部类访问局部变量必须声明为final?
- java中内部类访问局部变量为什么要定义局部变量为final
- JAVA匿名内部类不能访问外部类方法中的局部变量,除非变量被声明为final类型
- 局部内部类访问外部类的局部变量要求该变量为final类型
- JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?
- JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?
- 为什么局部内部类访问外边的局部变量必须为final?
- 为什么在方法中定义的内部类只能访问方法中的final类型的局部变量?
- 为什么在方法中定义的内部类只能访问方法中的final类型的局部变量?
- “局部内部类只能访问被final修饰的局部变量”的解释
- [Java] 匿名内部类访问外部类的局部变量为什么一定得是final类型
- java中内部类访问局部变量的时候,为什么变量必须加上final修饰
- Hadoop 2.2.0 测试出错处理
- 第一课:我想认识java
- 有关 Linux 下内核模块的开发方式的记录 .
- 【攻略】淘宝前端智勇大闯关-第二季
- iOS7.1 UISearchBar 去掉黑线..
- 【面试对宝典40题的解释】为什么从内部类中访问局部变量需要被声明为final最终类型
- IMP-00058: IMP-00000: 未成功终止导入
- UI组件: 使用命名空间,既保证私密性,又具有“隐秘”的开放性 (圣诞节彩蛋)
- php判断客户端浏览器的类型
- phpmailer发送邮件 SMTP Error: Could not authenticate 错误
- PHP5实现多态性的方案的分析
- 【美国】拉斯维加斯欲望之都夜生活
- C#.NET自动生成Excel图形报表
- 手游运营,怎么做一份数据日报?