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

在开发过程当中,因为习惯的缘由,咱们可能对某种编程语言的一些特性习觉得常,特别是只用一种语言做为平常开发的状况。可是当你使用超过一种语言进行开发的时候就会发现,虽然都是高级语言,可是它们之间不少特性都是不太相同的。java

现象描述

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

below-java8

可是在 Java 8 以后,相似场景却没有再提示了:闭包

normal-use

难道是此类变量能够随便改动了吗?固然不是,当你试图修改这些变量的时候,仍然会提示错误:编程语言

try-to-change

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

useage-in-kt

缘由分析

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

usage-in-kt

不难推断,这个 TestInnerClass$1.class 就是匿名内部类编译后的文件,看看它反编译后是什么内容:spa

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);
    }
}
复制代码

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

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

情景对比

可是为何对于 Kotlin 来讲能够在匿名内部类中直接修改基本数据类型的值呢?查看 Kotlin 编译后反编译回来的内容:cdn

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);
   }
复制代码

哈哈,并无画蛇添足,点个赞!

总结

我的猜想 Java 8 之因此作了这么个改进是由于引入了 Lambda 表达式以后,为了方便表达式内部访问外部变量时引入的一个 feature。它只是省去了咱们本身去将对外部变量声明成 final 的一个过程,让咱们感受像是支持了闭包,事实上却没有像其它语言如 JS 和 Kotlin 同样彻底支持。

相关文章
相关标签/搜索