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


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

    现象描述

    在 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);
       }
    

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

    作者:guanpj;来源:http://rrd.me/epfKR
    在这里插入图片描述
    欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升•职场突围•思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长

  • 相关阅读:
    搜索框用定时器限制发送请求
    vue的生命周期,钩子函数
    事件委托的实现流程
    在vscode中快速生成vue模板
    JS继承
    各种宽高
    ES6新特性
    python入门学习一
    字符编码
    npm install --save 与 npm install --save-dev 的区别
  • 原文地址:https://www.cnblogs.com/hejunlin/p/12891291.html
Copyright © 2020-2023  润新知