JDK8之前,局部内部类、匿名内部类访问局部变量为什么必须使用final修饰?

Java 1396℃

首先思考如下代码:

//抽象类
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修饰,可能就会导致程序运行的结果与预期不同。

转载请注明:零五宝典 » JDK8之前,局部内部类、匿名内部类访问局部变量为什么必须使用final修饰?