为什么java语法要求我们需要用final修饰呢?
想了想没有什么答案,那我们就通过jd-gui反编译工具一探究竟,我们对匿名内部类的字节码文件进行反编译得到以下内容。
我们可以看到匿名内部类的构造器中传入了一个参数,我们可以推理出这个参数就是底层传入的str的值,但因为反编译工具的某种疏忽将构造器的方法体写成了空,事实上真正的反编译代码应该是下面:
public class Hello$1 extends Thread {
private String val$str;
Hello$1(String paramString) {
this.val$str = paramString;
}
public void run() {
System.out.println(this.val$str);
}
}
也就是说匿名内部类之所以可以访问局部变量,是因为在底层将这个局部变量的值传入到了匿名内部类中,并且以匿名内部类的成员变量的形式存在,这个值的传递过程是通过匿名内部类的构造器完成的。
为什么需要用final修饰局部变量呢?
按照习惯,我依旧先给出问题的答案:用final修饰实际上就是为了保护数据的一致性。
这里所说的数据一致性,对引用变量来说是引用地址的一致性,对基本类型来说就是值的一致性。
这里我插一点,final修饰符对变量来说,深层次的理解就是保障变量值的一致性。为什么这么说呢?因为引用类型变量其本质是存入的是一个引用地址,说白了还是一个值(可以理解为内存中的地址值)。用final修饰后,这个这个引用变量的地址值不能改变,所以这个引用变量就无法再指向其它对象了。
为什么需要用final保护数据的一致性呢?
因为将数据拷贝完成后,如果不用final修饰,则原先的局部变量可以发生变化。这里到了问题的核心了,如果局部变量发生变化后,匿名内部类是不知道的(因为他只是拷贝了局不变量的值,并不是直接使用的局部变量)
这里举个栗子:
原先局部变量指向的是对象A,在创建匿名内部类后,匿名内部类中的成员变量也指向A对象。但过了一段时间局部变量的值指向另外一个B对象,但此时匿名内部类中还是指向原先的A对象。那么程序再接着运行下去,可能就会导致程序运行的结果与预期不同。
如果匿名内部类使用了局部变量,那么编译器会将使用的值拷贝一份,作为构造函数的一个参数传递进来(构造函数是编译器自动添加)。因为局部变量在方法或者代码块执行完毕,就会被销毁,所以编译器在编译的时候,就拷贝了一份局部变量存储的字面值或者地址值,这样局部变量被销毁时,匿名内部类依然拥有之前传递进来的值。
现在我们从语义上来理解下Java设计者的考虑:假如传递到匿名内部类的局部变量,不加final修饰,那么意味着局部变量可以改变,这就意味着匿名内部类里面值的变更和外部的变量的变更不能同步,虽然内部类持有的是局部变量值的拷贝,但是语义应该保持一致,语义保持一致的前提是值要能同步,因为java编译器的设计无法提供两边同步变更的机制,所以直接锁死,不允许内外变更~
参考:https://blog.csdn.net/tianjindong0804/article/details/81710268
https://www.cnblogs.com/xdouby/p/7845498.html