所谓闭包,说明白一点就是可以在一个函数中引用另一个函数定义的变量,这个变量称为自由变量。Java8通过lambda表达式支持这一点,但是该变量必须声明为final,究其实现,就能理解这个final的用意了。
定义下面的测试代码:
import java.util.*;
import java.lang.reflect.*;
public class Closure{
private static Runnable run;
public static void main(String...args)throws Exception{
work();
//为了证明调用栈之外还能引用
run.run();
Class<?> clazz = run.getClass();
System.out.println("run的类型:"+clazz+", 父类:"+clazz.getSuperclass()+", 实现的接口列表:");
Class<?>[] interfaces = clazz.getInterfaces();
Arrays.stream(interfaces).forEach(System.out::println);
Class<?> enclosing = clazz.getDeclaringClass();
System.out.println("外部类为:" + enclosing);
System.out.println("StaticInner外部类为:" + StaticInner.class.getDeclaringClass());
System.out.println("NonStaticInner外部类为:" + NonStaticInner.class.getDeclaringClass());
System.out.println("包路径:" + clazz.getPackage());
System.out.println("Closure的包路径:"+Closure.class.getPackage());
Field[] fs = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
System.out.println("通过反射获取其成员变量和值:");
Arrays.stream(fs).map(f->{
try{
f.setAccessible(true);
return String.format("%s=%s",f,f.get(run));
}catch(Exception e){
return null;
}
}).forEach(System.out::println);
System.out.println(clazz+"方法列表;");
Arrays.stream(methods).forEach(System.out::println);
}
public static void work(){
// Java lambda闭包
// 如果函数调用栈撤回那么这两个变量存放在何处?
// 不撤回函数调用栈管理会相当复杂
final Object obj = new Object();
int a = 9;
final Object obj1 = new Object();
work(()->{
System.out.println("输出引用的三个外部变量:(以----区分)");
System.out.println(obj + " ---- " + obj1 + " ----- " + a);
});
}
public static void work(Runnable run){
Closure.run = run;
run.run();
}
//用于证明getDeclaringClass可以获取到外部类
//静态内部类
public static class StaticInner{
}
//非静态内部类
public class NonStaticInner{
}
}
先讲讲这个测试的思路,work(Runnable run)是为了能引用lambda表达式,为了把闭包传递出去,我们定义了一个Closure类静态变量run。
另一个重载方法work()作为闭包产生的函数调用栈,work()内定义的三个变量obj、a、obj1会被定义的lambda表达式引用,这里就形成了闭包。
在main函数中,进行调用和类信息打印,可以印证原理。
在此先把结论提出,可以通过在测试代码打印出的信息中寻找证明答案:Java通过生成一个实现函数接口的类(非内部类),把引用的外部变量声明为private final成员变量。
把该代码复制进入Closure.java文件中,注意编码,切换到文件所在目录,运行下面命令:
javac -encoding utf-8 Closure.java
java Closure
结果截图:
从结果截图中可以看出,Java生成了一个名为Closure$$Lambda$1/303563356的类,实现的接口列表即为函数接口Runnable,通过getEnclosingClass()获取其外部类,返回值为null,实现函数接口的非内部类得证。
通过反射获取其成员变量,显然这些变量的数量和值跟所引用的外部变量并无二致,所以分析对象引用时,lambda引用同样需要计算。
被lambda的变量必须定义为final的原因就很明显了:java没有实现对引用的变量的直接修改。