细说匿名内部类引用方法局部变量时为什么需要声明为final
一、前言
在研究公司某个项目的源码,看到前人使用了挺多内部类,内部类平时我用的比较多的是匿名内部类,平时用的多的是匿名内部类,其他形式的用的比较少,然后我就有个疑惑:到底内部类是基于什么样的考虑,才让java设计者搞这么一套实现?还有,平时在什么情境下会考虑使用内部类呢?这个我将在另外一篇博文进行介绍,详情参见:xxxxxx,本篇博文是在查阅内部类相关资料时,看到很多博文在解释为什么匿名内部类在引用局部变量时需要将局部变量指定为final(不可变)说的不清楚,所以这边我将我的理解写出来,以供参考~
二、正文
本篇博文基于的Java环境是:jdk1.6,在jdk1.8中,匿名内部类引用局部变量时,已经废弃使用final修饰局部变量,但这并不意味着jdk1.8在实现方面有什么很大的变动,这个博文最后也会给予说明。
在讨论本篇博文之前,需要与看官达成一些共识:
1)Java中数据类型分为两大类:基本数据类型和引用数据类型。相应的,变量也分为基本类型和引用类型。基本类型的变量保存原始值,即它代表的值就是数值本身,而引用类型的变量保存引用值,"引用值"指向内存空间的地址(也就是地址值),代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置
2)final修饰的变量,赋予过初值之后,不允许变更。对基本类型而言,其保存的原始值不能变更,对引用类型而言,其引用的地址值不允许变更,也就是说不能引用其他对象
final int a = 1;a = 2;//编译报错final Integer b = new Integer(1);b = new Integer(2);//编译报错
先来个匿名内部类使用局部变量的例子:
public class OutClass {private String msg = "OutClass's msg";public void check(final String m){(new Thread(){public void run(){System.out.println(m);System.out.println(msg);}}).start();}public static void main(String[]args){OutClass oc = new OutClass();oc.check("Need to pass to inner Class");}} //输出 Need to pass to inner Class OutClass's msg
编译器编译的时候,匿名内部类会生成一个:外部类$x.class,其中x是正整数,这个实例中匿名内部类为:OutClass$1.class,我们可以使用jdk自带的反编译工具,将生成的这个class反编译出来看下,在dos窗口执行:javap -c OutClass$1.class,
我相信,只要熟悉内部类的童鞋,都知道,如果要实例化内部类,那么必须先实例化外部类,这就是原因所在,内部类需要持有外部类的一个引用,这也是内部类能无条件访问外部类所有成员方法和成员变量的根本原因。
通过上面反编译出来的内容可以看出,如果匿名内部类使用了局部变量,那么编译器会将使用的值拷贝一份,作为构造函数的一个参数传递进来(构造函数是编译器自动添加)。因为局部变量在方法或者代码块执行完毕,就会被销毁,所以编译器在编译的时候,就拷贝了一份局部变量存储的字面值或者地址值,这样局部变量被销毁时,匿名内部类依然拥有之前传递进来的值。现在我们从语义上来理解下Java设计者的考虑:假如传递到匿名内部类的局部变量,不加final修饰,那么意味着局部变量可以改变,这就意味着匿名内部类里面值的变更和外部的变量的变更不能同步,虽然内部类持有的是局部变量值的拷贝,但是语义应该保持一致,语义保持一致的前提是值要能同步,因为java编译器的设计无法提供两边同步变更的机制,所以直接锁死,不允许内外变更~
1 package com.finalparams.main; 2 3 public class Main { 4 public static void main(String[]args){ 5 Main m = new Main(); 6 m.show(); 7 } 8 9 public void show(){ 10 int a = 1; 11 (new innerClassInterface(){ 12 public void doChange(){ 13 a = 3;//编译报错:Cannot refer to the non-final local variable a defined in an enclosing scope 14 } 15 }).doChange(); 16 System.out.println(a); 17 } 18 } 19 20 interface innerClassInterface{ 21 public void doChange(); 22 }
以上代码第13行编译报错,假设编译器允许编译过去,按照编译器现有的实现,内部类里面的变更是没有办法反应到外面的, 也就是里面变更了,但是外部还是没变,这样是不能接受的,所以直接锁死,不让变更~
特别说明:在jdk1.8之后,匿名内部类使用局部变量的时候,局部变量已经不需要使用final修饰了,可以编译通过,那是不是说编译器的实现机制变了呢?非也,只是编译器在编译的时候不允许对这个变量进行变更,也就是说,如果你内外只是使用这个变量,而不进行重新赋值,那么就编译通过,如果内外有重新赋值,那么还是会报编译错误:
...................................................................................... public void show(){int a = 1;(new innerClassInterface(){public void doChange(){System.out.println(a);//jdk1.8之前编译报错,1.8之后,不报错,因为没有改变变量的值 }}).doChange();System.out.println(a);} ......................................................................................
public void show(){int a = 1;(new innerClassInterface(){public void doChange(){System.out.println(a);//jdk1.8之前编译报错,1.8之后,不报错,因为没有改变变量的值a = 4;//jdk1.8编译报错 }}).doChange();System.out.println(a);}
三、参考链接
https://www.zhihu.com/question/21395848
https://zhidao.baidu.com/question/266984801594415645.html
四、联系本人
为方便没有博客园账号的读者交流,特意建立一个企鹅群(纯公益,非利益相关),读者如果有对博文不明之处,欢迎加群交流:261746360,小杜比亚-博客园
转载于:https://www.cnblogs.com/xdouby/p/7845498.html
细说匿名内部类引用方法局部变量时为什么需要声明为final相关推荐
- 当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的.
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递 答:是值传递.Java 编程语言只有值传递参数.当一个对象实例作为一个参数被传 ...
- python 引用局部变量_在Python中赋值之前引用的局部变量?
我正在使用PyQt库获取网页的屏幕截图,然后阅读不同URL的CSV文件. 我保留一个变量Feed,每次处理URL时都会增加,因此应该增加到URL的数量. 这是代码: webpage = QWebPag ...
- java nextintln_java – 从lambda表达式引用的局部变量必须是final或者有效的final
我有一个JavaFX 8程序(适用于JavaFXPorts交叉平台),它实际上是为了做我想做的事情,但却是一步之遥.程序读取文本文件,对行进行计数以建立随机范围,从该范围中选取一个随机数并读取该行以进 ...
- JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?
本文主要记录:在JAVA中,(局部)内部类访问某个局部变量,为什么这个局部变量一定需要用final 关键字修饰? 首先,什么是局部变量?这里的局部是:在方法里面定义的变量. 因此,内部类能够访问某局部 ...
- c语言中的下标变量是什么,c语言引用数组元素时其数组下标的允许的数据类型是什么...
c语言引用数组元素时其数组下标的允许的数据类型是什么 发布时间:2020-07-30 11:56:52 来源:亿速云 阅读:621 作者:Leah c语言引用数组元素时其数组下标的允许的数据类型是什么 ...
- CSS在线字体库,外部字体的引用方法@font-face
[新增]使用自己下载的字体的示例: 参考 http://www.w3cplus.com/content/css3-font-face @font-face{ font-family: &quo ...
- List.add 方法添加元素时只会添加最后一条元素的问题与解决
List.add 方法添加元素时只会添加最后一条元素的分析解决 前言 一.问题描述 二.原因分析 1.简化分析 2.回归本题 总结 前言 在之前编写业务代码时, 遇到了一个比较神奇的现象, 如标题中描 ...
- vuex导入_vuex 引用方法
引入Vuex(前提是已经用Vue脚手架工具构建好项目) 1.利用npm包管理工具,进行安装 vuex.在控制命令行中输入下边的命令就可以了. npm install vuex --save 要注意的是 ...
- 使用控件时提示“未声明标识符“的解决方法
使用控件时提示"未声明标识符"的解决方法 参考文章: (1)使用控件时提示"未声明标识符"的解决方法 (2)https://www.cnblogs.com/Ro ...
- 【Groovy】编译时元编程 ( 方法拦截时用到的 AST 语法树节点 MethodNode 节点 | MethodNode 节点分析 | MethodNode 节点中的BlockStatement)
文章目录 一.方法拦截时用到的 AST 语法树节点 MethodNode 节点 二.MethodNode 节点分析 三.MethodNode 节点中的 BlockStatement 集合 一.方法拦截 ...
最新文章
- 存在多个不同注册中心的时候,如何平滑的统一注册中心?
- Linux-CentOS 重置root密码
- python线程与进程
- 实际操作之路考的这些事
- 从源码分析RocketMQ系列-RocketMQ消息持久化源码详解
- Dubbo自定义日志拦截器 1
- Hadoop 的常用组件一览
- c语言贪心算法合并箭,贪心算法:用最少数量的箭引爆气球
- 使用Jsoup 抓取页面的数据
- Spring框架之ioc
- orcale自定义数组并向数组中查询插入数据
- 网吧服务器系统ghost,深度网吧GHOST辅助工具
- Python自动化运维之1、Python入门
- 图解复盘总结和报告模板实例
- AutoJs 4.1.1 实战教程、Hamibot
- Android如何判断系统是否已经被Root
- CMMI的关键过程域(KPA)
- 告诉你喝水的14个惊人真相
- 什么是日志审计系统?性价比较高的日志审计系统有哪些?
- 信息学奥赛一本通 1404:我家的门牌号 | OpenJudge NOI 2.1 7649:我家的门牌号 | 小学奥数 7649
热门文章
- c语言中floor有什么作用,floor函数?floor函数的使用方法?ceil函数?C语言 floor 函数...
- 大数据毕设选题 - 深度学习植物识别系统(cnn opencv python)
- maven dependency 警告:Overriding managed version XXX for XXX
- CentOS yum安装mcrypt详细图解教程
- windows.old的删除方法
- 金额转大写java实现
- python房地产_如何用Python爬虫投资房产,走向人生巅峰?
- VS code(Visual Studio Code)乱码解决方法
- Socket,好像也挺简单,可是,真够烦
- 牛客多校9 B. Two Frogs (概率dp+前缀和优化)