在开发过程中, 由于习惯的原因, 我们可能对某种编程语言的一些特性习以为常, 特别是只用一种语言作为日常开发的情况. 但是当你使用超过一种语言进行开发的时候就会发现, 虽然都是高级语言, 但是它们之间很多特性都是不太相同的.
现象描述
在 Java 8 之前, 匿名内部类在使用外部成员的时候, 会报错并提示 "Cannot refer to a non-final variable arg inside an inner class defined in a different method":
但是在 Java 8 之后, 类似场景却没有再提示了:
难道是此类变量可以随便改动了吗? 当然不是, 当你试图修改这些变量的时候, 仍然会提示错误:
可以看到, 当试图修改基本数据类型的变量时, 编译器的警告变成了 "Varible'num'is accessed from within inner class, need to be final or effectively final", 很遗憾, 仍然不能修改. 相比之下, Kotlin 是没有这个限制的:
原因分析
从表面上当然看不出什么原因, 看看编译器做了什么工作吧! 运行 javac 命令后生成了几个 .class 文件:
不难推断, 这个 TestInnerClass$1.class 就是匿名内部类编译后的文件, 看看它反编译后是什么内容:
原来, 匿名也会被当作普通的类处理, 只不过编译器生成它构造方法的时候, 除了将外部类的引用传递了过来, 还将基本数据类型的变量复制了一份过来, 并把引用数据类型的变量引用也传递了过来. 因此, 基本数据类型的变量当然不能修改了, 不然就会跟外部的变量产生不一致, 这样的话变量的传递也就变得毫无意义了.
情景对比
但是为什么对于 Kotlin 来说可以在匿名内部类中直接修改基本数据类型的值呢? 查看 Kotlin 编译后反编译回来的内容:
可以发现, 当需要传递基本数据类型的变量时, Kotlin 编译器会将这些数据进行包装, 从而由值传递变为引用传递, 这样内部的修改当然就不会影响到外部了.
验证一下, 当变量不进行传递时, Kotlin 编译器是怎么处理的:
来源: http://developer.51cto.com/art/201908/602099.htm