首先思考如下代码:
//抽象类 abstract class Person{ public abstract void say(); } public class Test { public static void main(String[] args) { //局部变量 int age = 10; //匿名内部类 new Person() { @Override public void say() { System.out.println("我"+age+"岁了"); } }.say(); } }
如果在Java 8以前编译上述代码就会报错,在java 8及以后就不会报错。因为被局部内部类、匿名内部类访问的局部变量必须使用final修饰,Java8以后会为该局部变量自动使用了final修饰(语法糖)。那么问题来了,为什么需要使用final修饰?
想了想还是没有什么答案,那就通过jd-gui反编译工具一探究竟,可以看到上述源码编译后得到三个字节码文件(Person.class、Test$1.class、Test.class),其中Test$1.class就是匿名内部类的字节码文件,对其进行反编译得到以下内容:
class Test$1 extends Person{ Test$1(int paramInt) {}//匿名内部类的构造器 public void say(){ System.out.println("我" + this.val$age + "岁了"); } }
可以看到匿名内部类的构造器中传入了一个参数paramInt,这个参数就是底层传入的局部变量age的值,但因为反编译工具的某种疏忽将构造器的方法体写成了空,事实上真正的反编译代码应该是:
class Test$1 extends Person{ //匿名内部类的构造器 Test$1(int paramInt) { this.val$str = paramString; } public void say(){ System.out.println("我" + this.val$age + "岁了"); } }
也就是说匿名内部类之所以可以访问局部变量,是因为在底层将这个局部变量的值传入到了匿名内部类中,并且以匿名内部类的成员变量的形式存在,这个值的传递过程是通过匿名内部类的构造器完成的。
用final修饰实际上就是为了保护数据的一致性。final修饰符对变量来说,深层次的理解就是保障变量值的一致性。为什么这么说呢?因为引用类型变量其本质是存入的是一个引用地址,说白了还是一个值(可以理解为内存中的地址值)。用final修饰后,这个这个引用变量的地址值不能改变,所以这个引用变量就无法再指向其它对象了。如果不强制使用final修饰,可能就会导致程序运行的结果与预期不同。