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

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

给个简单示例:

     public static void main(String[] args) {
ArrayList<Father> list = new ArrayList<>();
for(int i = 0;i<10;i++){
list.add(showA(i));
}
for(int i = 0;i<10;i++){
list.get(i).show();
}
} static abstract class Father{
abstract void show();
} static Father showA(final int num){
class A extends Father{
void show(){
System.out.println(num);
}
}
A a = new A();
a.show();
return a;
}

代码执行两个循环,第一个循环使用方法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类型,不允许进行二次修改。

关于final局部变量引用的研究的更多相关文章

  1. 局部内部类为什么只能访问final局部变量,对于成员变量却可以随便访问?

    局部内部类为什么只能访问final局部变量,对于成员变量却可以随便访问? public class OuterClass { private int memberField = 10; public ...

  2. python之局部变量引用赋值前的结果

    通过正则表达式,实现加减 昨晚在做计算器的时候,被一个BUG搞懵比了.现在再看看,发现我好小白啊~~ #++- num = input("please input:") sa = ...

  3. javascript中值传递与值引用的研究

    今天重新看了一下<javascript高级程序设计>,其中讲到了javascript中的值传递和值引用,所以就自己研读了一下,但是刚开始没有明白函数中的参数只有值传递,有的场景好像参数是以 ...

  4. Java中final修饰符深入研究

    一.开篇 本博客来自:http://www.cnblogs.com/yuananyun/ final修饰符是Java中比较简单常用的修饰符,同时也是一个被"误解"较多的修饰符.对很 ...

  5. python局部变量引用问题

    a = [1, 2] b = 'Immutable' def test(): # global b print(a) a.append('asd') b = b + 'asd' # 当只是引用变量b的 ...

  6. Block循环引用问题研究

    自从苹果在objc中添加Block功能支持以后已经过了很久.目前网上对于Block的使用有很多介绍.不过对于Block的内存管理问题,则是众说纷纭.再加上objc开始使用ARC以后,对于Block的内 ...

  7. 局部内部类访问方法中的局部变量为什么加final

    转载:http://www.cnblogs.com/mjblogs/p/4971630.html 1)从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形 ...

  8. final修饰符(3)-基本类型变量和引用类型变量的区别

    final修饰基本类型变量 当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变 final修饰引用类型变量 当使用final修饰引用类型变量时,它保存的仅仅是一 ...

  9. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

随机推荐

  1. EpiiServer 更快捷更方便的php+nginx环境定制化方案

    EpiiServer是什么 更快捷更方便的php+nginx多应用部署环境. github仓库首页 https://github.com/epaii/epii-server gitee仓库 https ...

  2. JS正则表达式笔记

    正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串.将匹配的子串替换或者从某个串中取出符合某个条件的子串等. 正则 描述 ...

  3. MySQL分区的限制(最多有多少个分区)

    MySQL分区的限制 •   只能对数据表的整型列进行分区,或者数据列可以通过分区函数转化成整型列 •   最大分区数目不能超过1024 •   如果含有唯一索引或者主键,则分区列必须包含在所有的唯一 ...

  4. CSS3单选动画

    本示例实现了两种单选按钮动画效果,一种是缩放,一种是旋转,以下是html布局以及css样式 html:这里使用了label标签的for属性,以此来绑定radio <div class=" ...

  5. C++11中std::bind的使用

    std::bind: Each argument may either be bound to a value or be a placeholder: (1).If bound to a value ...

  6. LeetCode:3.Longest Substring Without Repeating Characters

    思路:看到题目首先想到最大字符串匹配KMP算法 public static int lengthOfLongestSubstring(String s) { int maxLength = 0; St ...

  7. 利用LD_PRELOAD进行hook

    原文地址:http://hbprotoss.github.io/posts/li-yong-ld_preloadjin-xing-hook.html 好久没玩hook这种猥琐的东西里,今天在Linux ...

  8. 第六篇 常用请求协议之post put patch 总结

    [转]https://blog.csdn.net/sshfl_csdn     感谢愿意总结分享的人,thanks idempotent 幂等的 如果一个方法重复执行多次,产生的效果是一样的,那就是i ...

  9. COJ 2192: Wells弹键盘 (dp)

    2192: Wells弹键盘 Description Wells十分羡慕和佩服那些会弹钢琴的人比如子浩君,然而Wells只会弹键盘…… Wells的键盘只有10个键,从1,2,3,……,9,0,如下图 ...

  10. LINQ学习笔记——(2)Lambda表达式

    最基本的 Lambda 表达式语法: (参数列表)=>{方法体} 说明:   参数列表中的参数类型可以是明确类型或者是推断类型   如果是推断类型,则参数的数据类型将由编译器根据上下文自动推断出 ...