原理 - Java - Java闭包实现细节

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/lidelin10/article/details/98316138

所谓闭包,说明白一点就是可以在一个函数中引用另一个函数定义的变量,这个变量称为自由变量。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没有实现对引用的变量的直接修改。

猜你喜欢

转载自blog.csdn.net/lidelin10/article/details/98316138
今日推荐