转载

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

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

扫描下方二维码 试读

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

  • 现象描述

  • 原因分析

  • 情景对比

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

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

现象描述

在 Java 8 之前,匿名内部类在使用外部成员的时候,会报错并提示

“Cannot refer to a non-final variable arg inside an inner class defined in a different method”:

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

below-java8.jpg

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

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

normal-use.jpg

难道是此类变量可以随便改动了吗?

当然不是,当你试图修改这些变量的时候,仍然会提示错误:

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

img

try-to-change.jpg

可以看到,当试图修改基本数据类型的变量时,编译器的警告变成了

“Varible 'num' is accessed from within inner class, need to be final or effectively final”

很遗憾,仍然不能修改。相比之下,Kotlin 是没有这个限制的:

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

usage-in-kt.jpg

原因分析

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

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

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

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

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

End

作者: guanp

来源:

https://www.jianshu.com/p/acc8d9a67d0c

本文版权归作者所有

长按下图二维码,即刻关注【 狸猫技术窝

阿里、京东、美团、字节跳动

顶尖技术专家 坐镇

为IT人打造一个 “有温度” 的技术窝!

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

原文  http://mp.weixin.qq.com/s?__biz=MzU2Njg3OTU1Mg==&mid=2247484815&idx=1&sn=001fb8934733c50d428e5f3d2f79cbf1
正文到此结束
Loading...