为什么必须是final的呢?
一个谜团
如果你用过类似guava这种“伪函数式编程”风格的library的话,那下面这种风格的代码对你来说应该不陌生:
1 2 3 4 5 6 7 8 9 |
|
这段代码对一个字符串的list进行过滤,从中找出长度为4的字符串。看起来很是平常,没什么特别的。
但是,声明expectedLength时用的那个final看起来有点扎眼,把它去掉试试:
error: local variable expectedLength is accessed from within inner class; needs to be declared final
结果Java编译器给出了如上的错误,看起来匿名内部类只能够访问final的局部变量。但是,为什么呢?其他的语言也有类似的规定吗?
在开始用其他语言做实验之前我们先把问题简化一下,不要再带着guava了,我们去除掉噪音,把问题归结为:
为什么Java中的匿名内部类只可以访问final的局部变量呢?其他语言中的匿名函数也有类似的限制吗?
Scala中有类似的规定吗?
1 2 3 4 5 6 7 8 9 10 11 12 |
|
上面的Scala代码是合法的,number变量是声明为var的,不是val(类似于Java中的final)。而且在匿名函数中可以修改number的值。
看来Scala中没有类似的规定。
C#中有类似的规定吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
这段C#代码也是合法的,number这个局部变量在lambda表达式内外都可以访问和赋值。
看来C#中也没有类似的规定。
分析谜团
三门语言中只有Java有这种限制,那我们分析一下吧。先来看一下Java中的匿名内部类是如何实现的:
先定义一个接口:
1 2 3 |
|
然后创建这个接口的匿名子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
这个匿名子类会被编译成一个单独的类,反编译的结果是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
可以看到名为number的局部变量是作为构造方法的参数传入匿名内部类的(以上代码经过了手动修改,真实的反编译结果中有一些不可读的命名)。
如果Java允许匿名内部类访问非final的局部变量的话,那我们就可以在TryUsingAnonymousClass$1中修改paramInteger,但是这不会对number的值有影响,因为它们是不同的reference。
这就会造成数据不同步的问题。
所以,谜团解开了:Java为了避免数据不同步的问题,做出了匿名内部类只可以访问final的局部变量的限制。
但是,新的谜团又出现了:
Scala和C#为什么没有类似的限制呢?它们是如何处理数据同步问题的呢?
上面出现过的那段Scala代码中的lambda表达式会编译成这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
可以看到number也是通过构造方法的参数传入的,但是与Java的不同是这里的number不是直接传入的,是被IntRef包装了一层然后才传入的。对number的值修改也是通过包装类进行的:this.number$2.elem = 456;
这样就保证了lambda表达式内外访问到的是同一个对象。
再来看看C#的处理方式,反编译一下,发现C#编译器生成了如下的一个类:
1 2 3 4 5 6 7 8 9 10 |
|
把number包装在这个类内,这样就保证了lambda表达式内外使用的都是同一个number,即便重新赋值也可以保证内外部的数据是同步的。
小结
Scala和C#的编译器通过把局部变量包装在另一个对象中,来实现lambda表达式内外的数据同步。
而Java的编译器由于未知的原因(怀疑是为了图省事儿?)没有做包装局部变量这件事儿,于是就只好强制用户把局部变量声明为final才能在匿名内部类中使用来避免数据不同步的问题。
转载于:https://www.cnblogs.com/snake-hand/p/3151172.html
为什么必须是final的呢?相关推荐
- public static final int REMIN_REQUEST_CODE = 0x911 自己的大致理解
public static final int REMIN_REQUEST_CODE = 0x911; 自己理解为 一个静态常量,也就一个标识,自己目前主要在2个地方常用到 OnActivityRes ...
- java内部类的权限符,static介绍、内部类、final、权限修饰符的作用范围,
static介绍.内部类.final.权限修饰符的作用范围,static 关键字:(可用于修饰方法.变量) static 特点: static是静态修饰符,一般修饰成员变量.被static修饰的属于全 ...
- java增加final,Java8增加功能--Effectively final 功能
java8新增了很多功能,可以大大简化代码,这个系列将会一一辅助代码加以介绍. 局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加.ja ...
- java语言基础final_java语言中final的用法
许多程序设计语言都有自己的办法告诉编译器某个数据是"常数".常数主要应用于下述两个方面: (1) 编译期常数,它永远不会改变 (2) 在运行期初始化的一个值,我们不希望它发生变化 ...
- C++11之final关键字
一.禁用继承 C++11中允许将类标记为final,方法时直接在类名称后面使用关键字final,如此,意味着继承该类会导致编译错误. 实例如下: class Super final {//...... ...
- (1)访问控制 (2)final关键字 (3)对象创建的过程 (4)多态
1.访问控制(笔试题) 1.1 常用的访问控制符 public - 公有的 protected - 保护的 啥也不写 - 默认的 private - 私有的 1.2 访问控制符的比较 访问控制符 访问 ...
- 深入java_深入Java Final
JAVA关键字final用于修饰数据.方法或类,通常意味着"无法改变的",既数据不能改变,方法不能覆盖,类不能继承.一般采用final有两种原因:设计和效率.而随着JAVA版本的更 ...
- Java学习总结:11(final关键字)
final关键字 在Java中final称为终结器,在Java中可以使用final定义类.方法和属性. 一.使用final定义的类不能再有子类,即:任何类都不能继承以final声明的父类. 在设计类的 ...
- java this final_Java this、final等关键字总结
this 关键字this引用对象自身.它也可以在构造方法内部用于调用同一个类的其他构造方法. 隐藏的静态变量可以通过"类.静态变量"来引用,而隐藏的实例变量就需要使用"t ...
最新文章
- prim算法_最小生成树的本质是什么?Prim算法道破天机
- feign 第一次调用超时_Feign ,3步搞定 HTTP 请求
- 2019年,你需要关注这些Node API和Web框架
- JavaScript的键盘事件
- java 8 stream reduce详解和误区
- 如何远程调试部署在CloudFoundry平台上的nodejs应用
- php pdo fetchassoc,pdo执行fetch查询语句,出现500错误,请问应该怎么写
- memcached并发CAS模式
- pandas小记:pandas汇总统计函数
- Spring Cloud(二):服务注册与发现 Eureka【Finchley 版】
- 怎么把java文件编译为class文件
- 如何在VMware Workstation上安装Windows Home Server Beta“ Vail”
- bum报文_数据中心VxLAN技术概念和原理解读
- 各类邮箱谷歌邮箱、Outlook邮箱、雅虎邮箱的购买养号策略
- 新零售O2O商城系统要怎么开发,这些功能都要有
- 新物种爆炸:认知升级时代的新商业思维
- 用JAVA怎么做个视图_java问题 可以把用JFrame做出来的具有视图层的小程序放在桌面上当一个小软件吗?...
- Lamp 架构 搭建 论坛网站
- linux反复出现文件系统损坏,Linux日常维护之文件系统损坏后的修复
- HTB打靶(Active Directory 101 Mantis)
热门文章
- linux的网络地址配置,教你如何完成Linux网络地址配置
- oracle主从关系表查询,Oracle 主从表联合查询解决方法
- 广联达2018模板算量步骤_老师傅带你学造价,广联达GTJ2018图文详解,小白也能学会的软件...
- python实战演练_python实战演练(三)购物车程序
- mysql锁总结知乎_Mysql悲观锁乐观锁区别与使用场景
- win7计算机创建新用户,win7系统无法创建新用户的解决方法
- python 英文字符频率统计 采用降序方式输出_Python读取英文文件并记录每个单词出现次数后降序输出示例...
- Python,OpenCV轮廓属性、轮廓检测及绘制
- 5. 编程规范和编程安全指南--JavaScript
- keras 的 example 文件 babi_memnn.py 解析