查看原文
其他

8年开发,一直不知道 Java为什么要加 final 关键字!

guanpj 程序员老鬼 2022-07-01
来源:www.jianshu.com/p/acc8d9a67d0c


在开发过程中,由于习惯的原因,我们可能对某种编程语言的一些特性习以为常,特别是只用一种语言作为日常开发的情况。


但是当你使用超过一种语言进行开发的时候就会发现,虽然都是高级语言,但是它们之间很多特性都是不太相同的。

现象描述


在 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 就是匿名内部类编译后的文件,看看它反编译后是什么内容:
class TestInnerClass$1 extends InnerClass { TestInnerClass$1(TestInnerClass var1, int var2, DataBean var3) { super(var1); this.this$0 = var1; this.val$num = var2; this.val$bean = var3; }
void doSomething() { super.doSomething(); System.out.println("num = " + this.val$num); System.out.println("bean name is: " + this.val$bean.name); }}

原来,匿名内部类也会被当作普通的类处理,只不过编译器生成它构造方法的时候,除了将外部类的引用传递了过来,还将基本数据类型的变量复制了一份过来,并把引用数据类型的变量引用也传递了过来。

因此,基本数据类型的变量当然不能修改了,不然就会跟外部的变量产生不一致,这样的话变量的传递也就变得毫无意义了。

final 关键字除了能让类不能被继承之外,对应到这种场景,就是让变量也不能被重新赋值。

情景对比


但是为什么对于 Kotlin 来说可以在匿名内部类中直接修改基本数据类型的值呢?查看 Kotlin 编译后反编译回来的内容:
public final void useNestedClass(@NotNull final TestNestedClass.DataBean bean) { Intrinsics.checkParameterIsNotNull(bean, "bean"); final IntRef num = new IntRef();//---1 num.element = 1;//---2 String var3 = "before action, num = " + num.element; System.out.println(var3); <undefinedtype> nestedClass = new TestNestedClass.NestedClass() { public void doSomething() { num.element = 678;//---3 bean.setName("xyz"); String var1 = "num = " + num.element; System.out.println(var1); var1 = "bean name is: " + bean.getName(); System.out.println(var1); } }; nestedClass.doSomething(); String var4 = "after action, num = " + num.element;//---4 System.out.println(var4); }

可以发现,当需要传递基本数据类型的变量时,Kotlin 编译器会将这些数据进行包装,从而由值传递变为引用传递,这样内部的修改当然就不会影响到外部了

验证一下,当变量不进行传递时,Kotlin 编译器是怎么处理的:
public final void useNestedClass(@NotNull TestNestedClass.DataBean bean) { Intrinsics.checkParameterIsNotNull(bean, "bean"); int num = 1; String var3 = "before action, num = " + num; System.out.println(var3); int num = 678; var3 = "after action, num = " + num; System.out.println(var3); }


往期推荐

一键定位线上问题,这款故障诊断工具太棒了!

周末撸了一个简版的 Redis,轻松搞定高性能的 key-value 服务

再见IE浏览器,微软将删除个人版 Win10 中的 IE 11

SQL中 left join 左表合并去重实用技巧

有了这款免费开源的远程桌面工具,我卸载了向日葵


耗时数月整理出来的技术书籍免费分享给大家
扫描下方二维码,回复关键字【电子书】获取!
喜欢的这里报道
↘↘↘

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存