废话开头
最近在封装一个基于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对象。