查看原文
其他

终于明白为什么要加 final 关键字了!

MarkerHub 2022-11-04

小hub导读

final,n:决赛、期终考试,adj:最终的、最后的。

这是英语翻译,我只能帮你到这里了~


  • 作者:guanpj

  • https://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 就是匿名内部类编译后的文件,看看它反编译后是什么内容:

  1. classTestInnerClass$1extendsInnerClass{

  2. TestInnerClass$1(TestInnerClass var1, int var2, DataBean var3) {

  3. super(var1);

  4. this.this$0 = var1;

  5. this.val$num = var2;

  6. this.val$bean = var3;

  7. }

  8. void doSomething() {

  9. super.doSomething();

  10. System.out.println("num = "+ this.val$num);

  11. System.out.println("bean name is: "+ this.val$bean.name);

  12. }

  13. }

原来,匿名也会被当作普通的类处理,只不过编译器生成它构造方法的时候,除了将外部类的引用传递了过来,还将基本数据类型的变量复制了一份过来,并把引用数据类型的变量引用也传递了过来。因此,基本数据类型的变量当然不能修改了,不然就会跟外部的变量产生不一致,这样的话变量的传递也就变得毫无意义了。

情景对比

但是为什么对于 Kotlin 来说可以在匿名内部类中直接修改基本数据类型的值呢?查看 Kotlin 编译后反编译回来的内容:

  1. publicfinalvoid useNestedClass(@NotNullfinalTestNestedClass.DataBean bean) {

  2. Intrinsics.checkParameterIsNotNull(bean, "bean");

  3. finalIntRef num = newIntRef();//---1

  4. num.element = 1;//---2

  5. String var3 = "before action, num = "+ num.element;

  6. System.out.println(var3);

  7. <undefinedtype> nestedClass = newTestNestedClass.NestedClass() {

  8. publicvoid doSomething() {

  9. num.element = 678;//---3

  10. bean.setName("xyz");

  11. String var1 = "num = "+ num.element;

  12. System.out.println(var1);

  13. var1 = "bean name is: "+ bean.getName();

  14. System.out.println(var1);

  15. }

  16. };

  17. nestedClass.doSomething();

  18. String var4 = "after action, num = "+ num.element;//---4

  19. System.out.println(var4);

  20. }

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

验证一下,当变量不进行传递时,Kotlin 编译器是怎么处理的:

  1. publicfinalvoid useNestedClass(@NotNullTestNestedClass.DataBean bean) {

  2. Intrinsics.checkParameterIsNotNull(bean, "bean");

  3. int num = 1;

  4. String var3 = "before action, num = "+ num;

  5. System.out.println(var3);

  6. int num = 678;

  7. var3 = "after action, num = "+ num;

  8. System.out.println(var3);

  9. }

哈哈,并没有多此一举,点个赞!

(完)

MarkerHub文章索引:(点击阅读原文直达)

https://github.com/MarkerHub/JavaIndex


【推荐阅读】

高并发场景下限流,常见的限流算法、方案解析!

Redis 是如何实现点赞、取消点赞的?

Spring系列最全 69 道 面试题和详解

SpringBoot 整合Shiro实现动态权限加载更新+Session共享+单点登录

千万级 高并发 “秒杀” 架构设计(含源码)





好文!必须点赞

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

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