Java lambda函数序列化

废话开头

最近在封装一个基于mapreduce的分布式计算框架,涉及到了lambda函数的序列化,简单研究了一下jdk自带的lambda函数序列化,这里做一个小小的总结。

为了控制篇幅,另外写了一篇文章介绍lambda原理,Java lambda函数原理


1. lambda序列化的使用

为了实现lambda的序列化,自然需要将函数式接口继承Serializable。

@FunctionalInterface
public interface Function <T, R> extends java.util.function.Function <T, R>, Serializable {
    
}

lambda序列化的实现,运行main方法,程序输出3,说明map序列化到文件,后经过反序列化得到func对象,func对象正常运行,符合预期。

public class TestLambda {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Function<String,Integer> map = s -> s.length();
        serialize(map, new FileOutputStream("lambda.obj"));
        Function<String,Integer> func = (Function<String,Integer>)deserialize(new FileInputStream("lambda.obj"));
        Integer length = func.apply("abc");
        System.out.println(length);
    }

    public static void serialize(Object obj, OutputStream out) throws IOException {
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        out.flush();
        out.close();
    }

    public static Object deserialize(InputStream in) throws IOException, ClassNotFoundException {
        ObjectInputStream objIn = new ObjectInputStream(in);
        Object result = objIn.readObject();
        objIn.close();
        return result;
    }
}

看到这里,请先思考一下:java对象序列化,一般都是将对象的类信息和成员变量的值记录下来,然后反序列化时,程序创建该类的一个新对象,然后将记录下来的成员变量的值赋值给这个新创建的对象(这里不讨论单例的序列化,有兴趣的同学可以自行了解对象的序列化细节)。但是lambda对象的类是没有固定class文件,那么请问lambda对象是怎么序列化的呢?


2. lambda序列化

如果我们使用这个命令运行TestLambda:java -cp . -Djdk.internal.lambda.dumpProxyClasses=. TestLambda

程序运行完成后,会在当前目录生成两个class文件,TestLambda\$$Lambda$1.class和TestLambda\$$Lambda$4.class。

其中TestLambda$$Lambda$1.class是下面这行代码生成的。

Function<String,Integer> map = s -> s.length();

而TestLambda$$Lambda$4.class则是如下这行代码生成的。

Function<String,Integer> func = (Function<String,Integer>)deserialize(new FileInputStream("lambda.obj"));

这俩类结构一样,我们就只分析TestLambda$$Lambda$1.class

final class TestLambda$$Lambda$1 implements Function {
    private TestLambda$$Lambda$1() {
    }
    @Hidden
    public Object apply(Object var1) {
        return TestLambda.lambda$main$ed276983$1((String)var1);
    }
    private final Object writeReplace() {
        return new SerializedLambda(TestLambda.class, "Function", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 6, "TestLambda", "lambda$main$ed276983$1", "(Ljava/lang/String;)Ljava/lang/Integer;", "(Ljava/lang/String;)Ljava/lang/Integer;", new Object[0]);
    }
}

了解java序列化机制的同学知道,writeReplace方法的作用是在该类实例序列化时,并不会真正序列化该类的对象,而是将writeReplace方法返回的对象进行序列化。所以,lambda对象序列化的时候,真正序列化的对象是SerializedLambda对象。

该SerializedLambda对象记录如下信息:
        (1).lambda定义所在的Class对象                                                                
        (2).lambda实现的函数式接口的全限定名
        (3).lambda实现的函数式接口的方法名
        (4).lambda实现的函数式接口的方法签名
        (5).implMethodKind(这个含义我也没弄明白)
        (6).lambda函数的真正实现方法所在的类名,和(1)中的Class对象相同,这里是类的全限定名
        (7).lambda函数的真正实现方法的方法名
        (8).lambda函数的真正实现方法的方法签名
        (9).替换类型参数的函数式接口方法签名
        (10).捕获的参数

最终,这些信息,也就是上面代码中new SerializedLambda对象传进去的参数,才是被序列化记录的信息,这些信息都是在用于lambda的反序列化。


3.lambda反序列化

既然lambda序列化时记录的是SerializedLambda对象,那么我们来看SerializedLambda对象是怎么最终变成lambda对象的。

在SerializedLambda类的源码中,我们可以看到有一个方法readResolve,这个方法的作用是在该类对象被反序列化时,将调用readResolve方法,该方法的返回对象才是反序列化返回的对象。

下面就是SerializedLambda的readResolve方法

    private Object readResolve() throws ReflectiveOperationException {
        try {
            Method deserialize = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
                @Override
                public Method run() throws Exception {
                    Method m = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class);
                    m.setAccessible(true);
                    return m;
                }
            });

            return deserialize.invoke(null, this);
        }
        catch (PrivilegedActionException e) { ... }
    }

从代码中可以看出,反序列化SerializedLambda对象时,readResolve方法将会从capturingClass中寻找名为"$deserializeLambda$"的方法。由上一节可知,capturingClass就是定义lambda对象所在的类对象,而$deserializeLambda$方法也是编译器生成的隐藏方法,通过 javap -s -p -v -c -l TestLambda.class 查看字节码,可以看到确实存在方法$deserializeLambda$。

private static java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
...

由于这个方法字节码比较长,就不全部贴出来分析了,在这个方法中,会对上一节序列化记录的信息进行校验,校验正确,程序会调用invokeDynamic指令生成代表lambda的内部类,并且创建该内部类的对象,然后返回作为反序列化的最终对象。

简单来说,lambda对象的反序列化,就是先反序列化出SerializedLambda对象,然后调用readResolve方法生成真正的lambda对象。


4.总结

(1).编译器会在定义lambda函数的类中,生成静态方法$deserializeLambda$(java.lang.invoke.SerializedLambda),该方法知晓由SerializedLambda对象生成lambda对象的具体细节。

(2).lambda对象运行时生成的类,它有一个writeReplace方法,该方法将lambda的信息记录在一个SerializedLambda对象中,然后将这个SerializedLambda对象序列化。它记录了定义lambda所在的类,lambda实现的函数式接口等。

(3).SerializedLambda对象定义的readResolve方法,就是用于反序列化生成lambda对象的。

(4).lambda对象序列化整个流程:

lambda对象 --> writeReplace方法 --> SerializedLambda对象 --> 序列化的字节 --> SerializedLambda对象 --> readResolve方法 --> $deserializeLambda$方法 --> lambda对象。

 

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值