SerializedLambda是Java提供的关于lambda表达式的序列化方案,会将实现了Serializable接口的lambda表达式转换成 SerializedLambda 对象之后再去做序列化。其核心在于Java在对lambda表达式序列化时,虚拟机会添加一个writeReplace()方法。
根据Java的序列化机制,虚拟机在调用write(obj)序列化对象前,如果被序列化对象有writeReplace方法,则会先调用该方法,用该方法返回的对象进行序列化,即序列化对象被替换了。
同理,lambda表达式在序列化前也会调用writeReplace(),然后返回一个SerializedLambda 对象(真正的被序列化的对象),该对象包含了lambda表达式的所有信息,比如函数名implMethodName、函数签名implMethodSignature等,这些信息都是以字段形式存在的,这样就解决了lambda序列化的问题。
既然被序列化的对象有writeReplace()方法,那么我们也可以直接调用该方法获取相应的SerializedLambda对象,有了SerializedLambda对象一切就迎刃而解了。
interface NamedFunction<T, R> extends Function<T, R>, TypedName<R>, Serializable {
R apply(T t);
@Override
@SneakyThrows
default Class<R> type() {
//调用writeReplace()方法,返回一个SerializedLambda对象
SerializedLambda lambda = this.lambda();
sun.reflect.generics.parser.SignatureParser parser = SignatureParser.make();
MethodTypeSignature methodSig = parser.parseMethodSig(lambda.getImplMethodSignature());
ClassTypeSignature signature = (ClassTypeSignature) methodSig.getReturnType();
return (Class<R>) Class.forName(signature.getPath().get(0).getName());
}
@SneakyThrows
default String name() {
SerializedLambda lambda = lambda();
return lambda.getImplMethodName();
}
@SneakyThrows
default SerializedLambda lambda() {
Method method = this.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
//调用writeReplace()方法,返回一个SerializedLambda对象
return (SerializedLambda) method.invoke(this);
}
}
其它
- JDK中Lambda表达式的序列化与SerializedLambda的巧妙使用
SerializedLambda 类上的注释有四大段
段落一:SerializedLambda是Lambda表达式的序列化形式,这类存储了Lambda表达式的运行时信息。
段落二:编译器需确保生成的 lambda 类的实例会提供一个writeReplace 方法,且该方法会返回一个SerializedLambda实例。
段落三:SerializedLambda提供了readResolve方法,其职能为调用 capturingClass 的静态方法$deserializeLambda$(SerializedLambda)并且把自身实例作为入参。capturingClass 即为 lambda 表达式定义所在的类。
段落四: 序列化和反序列化产生的函数对象的身份敏感操作的标识形式(如System.identityHashCode()、对象锁定等等)是不可预测的。
- Serialize a Lambda in Java
When the JVM encounters a lambda expression, it will use the built-in ASM to build an inner class. So, what does this inner class look like? We can dump this generated inner class by specifying the jdk.internal.lambda.dumpProxyClasses property on the command line:
-Djdk.internal.lambda.dumpProxyClasses=<dump directory>
After dumping, we can inspect this generated inner class with an appropriate Java decompiler:
inspecting the compiled class file, we also need to inspect the newly generated inner class. NotSerializableLambdaExpression.lambda$getLambdaExpressionObject$0 method, which is generated by the Java compiler and represents our lambda expression implementation.
for serializable lambda expression Runnable r = (Runnable & Serializable) () -> System.out.println(“please serialize this message”);
In general
For tips, The main responsibility of the above $deserializeLambda$ method is to construct an object. First, it checks the SerializedLambda‘s getXXX methods with different parts of the lambda expression details. Then, if all conditions are met, it will invoke the SerializableLambdaExpression::lambda$getLambdaExpressionObject$36ab28bd$1 method reference to create an instance. Otherwise, it will throw an IllegalArgumentException. the code detail is expresed in the following picture.
When we use the ObjectOutputStream to serialize a lambda expression, the ObjectOutputStream will find the generated inner class contains a writeReplace method that returns a SerializedLambda instance. Then, the ObjectOutputStream will serialize this SerializedLambda instance instead of the original object. Next, when we use the ObjectInputStream to deserialize the serialized lambda file, a SerializedLambda instance is created. Then, the ObjectInputStream will use this instance to invoke the readResolve defined in the SerializedLambda class. And, the readResolve method will invoke the $deserializeLambda$ method defined in the capturing class. Finally, we get the deserialized lambda expression.