https://blog.csdn.net/wjw521wjw521/article/details/77333820

在java编程中,没用的类定义太多对系统来说也是一个负担,这时候我们可以通过定义匿名内部类来简化编程,但匿名内部类访问外部方法的成员变量时都要求外部成员变量添加final修饰符,final修饰变量代表该变量只能被初始化一次,以后不能被修改。但为什么匿名内部类访问外部成员变量就不允许他修改了呢?

接下来这个例子应该足够把这些说清楚了:

示例代码:

  1. public class InnerFinalTest {
  2. private static Test test0= null;
  3. public static void main(String[] args) {
  4. new InnerFinalTest().method1();
  5. System.out.println("-------");
  6. test0.test();
  7. }
  8. public void method1(){
  9. final Test  test = new Test();
  10. test0 = new Test(){
  11. @Override
  12. public void test(){
  13. System.out.println("匿名内部类:" + test);
  14. Field[] field = this.getClass().getDeclaredFields();
  15. for (int i = 0; i < field.length; i++) {
  16. System.out.println(field[i].getName());
  17. }
  18. }
  19. };
  20. InnerFinalTest ift = new InnerFinalTest();
  21. ift.innerFinalTest(test0);
  22. System.out.println("外部直接访问变量:"+ test);
  23. }
  24. public void innerFinalTest(Test test){
  25. test.test();
  26. }
  27. }

Test类无关紧要,不过还是贴一下他吧

  1. public class Test {
  2. public void test(){
  3. System.out.println("啊啊啊啊啊!" );
  4. }
  5. }

说明:

为什么我们要将被匿名内部类访问的变量定义成final呢?
首先,我们在InnerFinalTest类中定义了一个static变量test0:
private static Test test0= null;
该语句说明test0的生命周期和类一样
接下来在main方法中调用method1(),在method1()中将我们定义的匿名内部类赋给了test0,这说明如果test0不往别处指的话,我们匿名内部类将被一直引用着,
如同吃了九转大金丹,与天地同寿,与日月齐光,匿名内部类生命周期和InnerFinalTest类(匿名类的天地)相同了。
但是,method1()调用完了他要释放资源了,所以method1()方法中:

final Test  test = new Test();

test变量也要被释放了,test没了,但匿名内部类引用了test,如果java编译器不搞点小动作,他就没法玩儿了,因为匿名类的生命周期长,还使用着test,而外部变量先撤了,背后捅了匿名内部类一刀子。。。
匿名内部类说,就防着你这一招呢,所以叫编译器大哥帮忙搞了个小动作,明修栈道暗度陈仓,编译的时候,我自己把你给我的变量备份了一份,表面上看是我引用了你的变量,其实在运行期间我就用我自己备份的了。但是别人表面上看不知道我备份了一份,还以为我用的你的,如果不定义成final,变量在外面被修改了,我没改,那我的结果就会和预期不同,为了防止出现这种情况,所以要被定义成final。

上面实例代码运行结果:

匿名内部类:Test@40e455bf
this$0
val$test
外部直接访问变量:Test@40e455bf
-------
匿名内部类:Test@40e455bf
this$0
val$test

我用反射证明了匿名内部类存在外部变量的备份val$test,其中因为变量是默认类型,所以使用getDeclaredFields得到所有匿名内部类运行期间存在的成员属性,注意,该成员属性在编码期间是不存在的,
是编译器主动为匿名内部类添加的成员属性,所以可以通过反射在运行期间一窥究竟。

如果去掉匿名内部类对外部变量的引用,如去掉以下代码:
System.out.println("匿名内部类:" + test);
运行结果中会没有了val$test,这也再次证明了以上结论:匿名内部类备份了变量。

通过外部变量和内部变量打印内容相同,说明两个变量test和val$test的变量引用指向的内存区域是相同的,(这里可以参考一下原型模式浅克隆)。指向相同对象,虽然对象不能修改,但对象中的属性可以修改,而匿名内部类变量和外部变量指向相同,自然值也同步修改了。

总结一下,逻辑应该是这样的:为了解决生命周期不同的问题,匿名内部类备份了变量,为了解决备份变量引出的问题,外部变量要被定义成final
我们匿名内部类使用final不是怕修改,是怕不能同步修改

匿名内部类访问方法成员变量需要加final的原因及证明(转)的更多相关文章

  1. 【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态

    一. Objective-C 方法详解 1. 方法属性 (1) OC 方法传参机制 Object-C 方法传参机制 : OC 中得参数传递都是值传递, 传入参数的是参数的副本; -- 基本类型 (值传 ...

  2. 为什么内部类访问的外部变量需要使用final修饰

    因为生命周期的原因.方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象.首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而 ...

  3. 为什么在匿名内部类中引用外部对象要加final修饰符

    当所在的方法的形参需要被内部类里面使用时,该形参必须为final. 为什么必须要为final呢? 首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一clas ...

  4. JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?

    本文主要记录:在JAVA中,(局部)内部类访问某个局部变量,为什么这个局部变量一定需要用final 关键字修饰? 首先,什么是局部变量?这里的局部是:在方法里面定义的变量. 因此,内部类能够访问某局部 ...

  5. Java 继承关系中:static,构造函数,成员变量的加载顺序

    首先看下面的例子: package simple.demo; /** * @author Administrator * @date 2019/01/03 */ public class ClassA ...

  6. php访问方法外变量

    class Capture { private static $_CapSite = 222; function dd() { echo self::$_CapSite; } } $cc=new Ca ...

  7. Java中变量之局部变量、本类成员变量、父类成员变量的访问方法

    变量:局部变量.本类成员变量.父类成员变量 如何访问:如果变量名相同,则采用就近原则,哪个变量离所要调用的访问最近,那就么就输出,优先顺序为:局部变量 > 本类成员变量 > 父类成员变量 ...

  8. 为什么Java匿名内部类访问的外部局部变量或参数需要被final修饰

    大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...

  9. 测试 Java 类的非公有成员变量和方法

    引言 对于软件开发人员来说,单元测试是一项必不可少的工作.它既可以验证程序的有效性,又可以在程序出现 BUG 的时候,帮助开发人员快速的定位问题所在.但是,在写单元测试的过程中,开发人员经常要访问类的 ...

随机推荐

  1. (19)jQuery操作文本和属性

    <!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>jq ...

  2. 转载 React.createClass 对决 extends React.Component

    先给出结论,这其实是殊途同归的两种方式.过去我们一般都会使用 React.createClass 方法来创建组件,但基于 ES6 的小小语法糖,我们还可以通过 extends React.Compon ...

  3. linux I/O状态实时监控iostat

    首先查看系统有没有安装sysstat 如果没有,则yum install sysstat -y [root@bogon ~]# iostat -c 2 2 #显示cpu状态信息 Linux 3.10. ...

  4. C++中的ravalue学习笔记

    一.学习笔记 1. A a = 42; 会先以42为参数构造一个A类对象,然后调用拷贝构造函数来构造a,目前编译器优化掉了拷贝构造函数的调用,测试拷贝构造函数是没有被调用的,但是其权限不能为priva ...

  5. Android资源收集

    1 android studio最新版本下载地址:https://developer.android.google.cn/studio/preview/?utm_source=android-stud ...

  6. Golang 端口复用测试

    先给出结论: 同一个进程,使用一个端口,然后连接关闭,大约需要30s后才可再次使用这个端口. 测试 首先使用端口9001连接服务端,发送数据,然后关闭连接,接着再次使用端口9001连接服务端,如果连接 ...

  7. 文件权限控制--umask

    问题 有一个场景,在A机器上有一个可执行文件,将其压缩,然后拷贝到B机器上,解压,发现原来文件的可执行权限消失了. 原因 B机器上,当前解压用户的umask设置,限制了新创建文件的权限. umask是 ...

  8. golang语言并发与并行——goroutine和channel的详细理解(一) 转发自https://blog.csdn.net/skh2015java/article/details/60330785

    如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. ...

  9. 源代码安装Apache、Mysql、PHP

    源代码软件的优点:     获得最新版,能及时修复bug:     能自行修改和定制: 源代码打包形式:     .tar.gz和.tar.bz2格式居多: 完整性校验:     md5sum校验工具 ...

  10. Maven Docker部署

    镜像构建方式 docker-maven-plugin插件构建docker镜像有两种方式: 1. 指定参数,由docker-maven-plugin插件根据这些参数来制作镜像: 2. 指定Dockerf ...