Java 方法内匿名类引用局部变量必须用 final 修饰(个人理解)

JDK 1.8 之前需要用 final 修饰,否则编译器会报错。JDK 1.8 及以后增加语法糖,不用显式地用 final 修饰了,但需要知道,编译器已经为你做了这一步。

对于 "为什么 Java 方法内匿名类引用局部变量必须用 final 修饰?" 这个问题,我觉得先不必纠结 final 修饰符。

首先明确两个条件,一是 Java 方法内的匿名类,而不是任何其他的匿名类,包括以下的例子也是合法的。

public class Demo {
	
	Pattern pattern = Pattern.compile("*.jpg");
	
	FilenameFilter ff = new FilenameFilter() {
		@Override
		public boolean accept(File dir, String name) {
			Matcher matcher = pattern.matcher(name);
			return false;
		}
	};
}

二是,匿名类引用局部变量,而不是任何其他变量(成员变量等),以下例子是合法的。

public class Demo {
	
	
	static Pattern pattern = Pattern.compile("*.jpg");

	public static FilenameFilter getFilenameFilter() {

		return new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				pattern = Pattern.compile("*.gif");
				Matcher matcher = pattern.matcher(name);
				return false;
			}
		};
	}
}

需要知道,方法内的匿名类是可以访问方法的局部变量的,这是因为 Java 也从一定角度支持闭包,但并不纯正。

局部变量的生命周期伴随着方法执行的结束而结束,方法结束,局部变量的内存将被回收,这一块区域也是非法访问的。

但是,方法内的匿名类就是 Java 实现的一种闭包,外部可以通过 return 获取到方法内的匿名类,进而通过匿名类访问方法内的局部变量。

这从内存访问角度去看,是非常危险的!如果仅仅是读内存,大不了是访问了方法结束后被回收的内存块(可能是脏数据);但如果是写内存,这种行为则是非法的!除非 Java 能够将方法栈一直维持在内存中(不回收)。

为了避免这种危险操作,Java 做了一个秘密的操作,方法内的匿名类如果访问局部变量,则会拷贝一份局部变量的副本,成为自己的成员变量,那么这样,即使在方法结束后访问了,也只是访问的自己的成员变量,内存访问并不是非法的。

理论上来说,以上这种 "暗箱操作" 即使是读内存还是写内存,都是合法的,毕竟是自己的成员变量。

不过,从阅读代码的角度看,还是直观地误以为是修改的局部变量(进行非法内存的写操作)。Java 为了隐藏自己没有真正实现闭包的缺陷,干脆直接将局部变量修饰为 final,不允许你修改,这样就不用担心写内存的危险了。

猜你喜欢

转载自blog.csdn.net/qq_39291919/article/details/108859888