匿名内部类访问方法成员变量需要加final的原因及证明(转)
https://blog.csdn.net/wjw521wjw521/article/details/77333820
在java编程中,没用的类定义太多对系统来说也是一个负担,这时候我们可以通过定义匿名内部类来简化编程,但匿名内部类访问外部方法的成员变量时都要求外部成员变量添加final修饰符,final修饰变量代表该变量只能被初始化一次,以后不能被修改。但为什么匿名内部类访问外部成员变量就不允许他修改了呢?
接下来这个例子应该足够把这些说清楚了:
示例代码:
- public class InnerFinalTest {
- private static Test test0= null;
- public static void main(String[] args) {
- new InnerFinalTest().method1();
- System.out.println("-------");
- test0.test();
- }
- public void method1(){
- final Test test = new Test();
- test0 = new Test(){
- @Override
- public void test(){
- System.out.println("匿名内部类:" + test);
- Field[] field = this.getClass().getDeclaredFields();
- for (int i = 0; i < field.length; i++) {
- System.out.println(field[i].getName());
- }
- }
- };
- InnerFinalTest ift = new InnerFinalTest();
- ift.innerFinalTest(test0);
- System.out.println("外部直接访问变量:"+ test);
- }
- public void innerFinalTest(Test test){
- test.test();
- }
- }
Test类无关紧要,不过还是贴一下他吧
- public class Test {
- public void test(){
- System.out.println("啊啊啊啊啊!" );
- }
- }
说明:
为什么我们要将被匿名内部类访问的变量定义成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的原因及证明(转)的更多相关文章
- 【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态
一. Objective-C 方法详解 1. 方法属性 (1) OC 方法传参机制 Object-C 方法传参机制 : OC 中得参数传递都是值传递, 传入参数的是参数的副本; -- 基本类型 (值传 ...
- 为什么内部类访问的外部变量需要使用final修饰
因为生命周期的原因.方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象.首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而 ...
- 为什么在匿名内部类中引用外部对象要加final修饰符
当所在的方法的形参需要被内部类里面使用时,该形参必须为final. 为什么必须要为final呢? 首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一clas ...
- JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?
本文主要记录:在JAVA中,(局部)内部类访问某个局部变量,为什么这个局部变量一定需要用final 关键字修饰? 首先,什么是局部变量?这里的局部是:在方法里面定义的变量. 因此,内部类能够访问某局部 ...
- Java 继承关系中:static,构造函数,成员变量的加载顺序
首先看下面的例子: package simple.demo; /** * @author Administrator * @date 2019/01/03 */ public class ClassA ...
- php访问方法外变量
class Capture { private static $_CapSite = 222; function dd() { echo self::$_CapSite; } } $cc=new Ca ...
- Java中变量之局部变量、本类成员变量、父类成员变量的访问方法
变量:局部变量.本类成员变量.父类成员变量 如何访问:如果变量名相同,则采用就近原则,哪个变量离所要调用的访问最近,那就么就输出,优先顺序为:局部变量 > 本类成员变量 > 父类成员变量 ...
- 为什么Java匿名内部类访问的外部局部变量或参数需要被final修饰
大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...
- 测试 Java 类的非公有成员变量和方法
引言 对于软件开发人员来说,单元测试是一项必不可少的工作.它既可以验证程序的有效性,又可以在程序出现 BUG 的时候,帮助开发人员快速的定位问题所在.但是,在写单元测试的过程中,开发人员经常要访问类的 ...
随机推荐
- json(传输格式)、异步加载、时间线
xml:过去传输的数据格式 json:现在的传输数据格式,属性名加双引号来区别,其实也是对象,传输的是个字符串,其实就是json 前端JSON.stringfy(obj) 然后传给后台 后台传回来的j ...
- <jsp:include>动作元素,附:最易出错的一点
先定义一个date.jsp,再定义一个main.jsp.用<jsp:include plage = "相对url地址" flush = "true"> ...
- 【BZOJ3992】【SDOI2015】序列统计
数论劲啊 原题: 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小 ...
- 【BZOJ1213】高精度开根
python是坠吼的! 原题: 不贴原题,就是高精度开根,结果向下取整 首先二分答案,高精度嘛……python即可 二分右端点设为n会T掉,需要先倍增一个r,while(r **m <= n) ...
- OutputStream 和 Writer
OutputStream类(直接操作byte数组) 该类是字节输出流的抽象类,定义了输出流的各种操作方法.如下图是OutputStream的层次结构: ByteArrayOutputStream:字节 ...
- jsonDB使用手冊
已在github上建立项目:https://github.com/ThinkerCodeChina/jsonDB 博客:http://blog.csdn.net/thinkercode/ 简单介绍: ...
- mysql之 表空间传输
说明:MySQL(5.6.6及以上),innodb_file_per_table开启. 1.1. 操作步骤: 0. 目标服务器创建相同表结构1. 目的服务器: ALTER TABLE t DISCAR ...
- Linux下C与Mysql的混合编程(转)
1 概述 MySQL 是一个关系型数据库管理系统.由瑞典MySQL AB公司开发,眼下属于Oracle公司.MySQL是最流行的关系型数据库管理系统. 支持AIX.FreeBSD.HP-UX.Linu ...
- Go并发控制--context的使用
并发控制 Cancel Example 通过使用WithCancel可以取消一个或多个goroutine的执行,以实现对并发的控制. package main import ( "conte ...
- linux项目部署常用命令
原文出处:http://blog.csdn.net/u013628152/article/details/45847013 1:执行命令#find / -name tomcat,系统将列出所有tomc ...