查看原文
其他

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

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约


每日英文

Everyone would get tired.Nobody can take the sorrow for you.We some time need to grow up by ourselves.

每个人都会累,没人能为你承担所有伤悲,人总有一段时间要学会自己长大。


每日掏心

人生的三大悲:和憎恨的人生活在一起无法摆脱,你深爱的人离开了你,你很想得到的东西一直无法得到。拿得起,放得下,不强求,才能活出精彩。



来自:guanpj | 责编:乐乐

链接:jianshu.com/p/acc8d9a67d0c

程序员小乐(ID:study_tech)第 748 次推文   图片来自 Pexels


往日回顾:15岁成杀人犯,监狱里学编程,37岁获释后年薪70万



   正文   



在开发过程中,由于习惯的原因,我们可能对某种编程语言的一些特性习以为常,特别是只用一种语言作为日常开发的情况。但是当你使用超过一种语言进行开发的时候就会发现,虽然都是高级语言,但是它们之间很多特性都是不太相同的。


现象描述


在  Java 8 之前,匿名内部类在使用外部成员的时候,会报错并提示 “Cannot refer to a non-final variable arg inside an inner class defined in a different method”


below-java8.jpg


但是在 Java 8 之后,类似场景却没有再提示了:


normal-use.jpg


难道是此类变量可以随便改动了吗?当然不是,当你试图修改这些变量的时候,仍然会提示错误:


try-to-change.jpg


可以看到,当试图修改基本数据类型的变量时,编译器的警告变成了 “Varible 'num' is accessed from within inner class, need to be final or effectively final”,很遗憾,仍然不能修改。相比之下,Kotlin 是没有这个限制的:


usage-in-kt.jpg


原因分析


从表面上当然看不出什么原因,看看编译器做了什么工作吧!运行 javac令后生成了几个 .class 文件:


generated-files.jpg


不难推断,这个 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);
   }


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

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

前后端分离开发,六大方案全揭秘:HTTP API 认证授权术

浓缩就是精华!106页的《Python进阶》中文版(附下载)

IDEA的这几个调试的骚操作,用了都说爽!


关注「程序员小乐」,收看更多精彩内容
嘿,你在看吗?
: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

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

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