ClassCastException
is thrown by Java8 upon deserializing a lambda when following conditions are met:
Serializable
lambdaTested on Oracle Java compiler and runtime versions 1.8.0_91. Please find test code on how to reproduce:
import java.io.*;
/**
* @author Max Myslyvtsev
* @since 7/6/16
*/
public class LambdaSerializationTest implements Serializable {
static abstract class AbstractConverter implements Serializable {
String convert(String input) {
return doConvert(input);
}
abstract String doConvert(String input);
}
static class ConverterA extends AbstractConverter {
@Override
String doConvert(String input) {
return input + "_A";
}
}
static class ConverterB extends AbstractConverter {
@Override
String doConvert(String input) {
return input + "_B";
}
}
static class ConverterC extends AbstractConverter {
@Override
String doConvert(String input) {
return input + "_C";
}
}
interface MyFunction<T, R> extends Serializable {
R call(T var);
}
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("java.version"));
ConverterA converterA = new ConverterA();
ConverterB converterB = new ConverterB();
ConverterC converterC = new ConverterC();
giveFunction(converterA::convert);
giveFunction(converterB::convert);
giveFunction(converterC::convert);
}
private static void giveFunction(MyFunction<String, String> f) {
f = serializeDeserialize(f);
System.out.println(f.call("test"));
}
private static <T> T serializeDeserialize(T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
@SuppressWarnings("unchecked")
T result = (T) ois.readObject();
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It gives following output:
1.8.0_91
test_A
Exception in thread "main" java.lang.RuntimeException: java.io.IOException: unexpected exception type
at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:68)
at LambdaSerializationTest.giveFunction(LambdaSerializationTest.java:52)
at LambdaSerializationTest.main(LambdaSerializationTest.java:47)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.io.IOException: unexpected exception type
at java.io.ObjectStreamClass.throwMiscException(ObjectStreamClass.java:1582)
at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1154)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1817)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:65)
... 7 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.lang.invoke.SerializedLambda.readResolve(SerializedLambda.java:230)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1148)
... 11 more
Caused by: java.lang.ClassCastException: LambdaSerializationTest$ConverterB cannot be cast to LambdaSerializationTest$ConverterA
at LambdaSerializationTest.$deserializeLambda$(LambdaSerializationTest.java:7)
... 21 more
Upon decompiling this $deserializeLambda$
method with CFR following code is revealed:
private static /* synthetic */ Object $deserializeLambda$(SerializedLambda lambda) {
switch (lambda.getImplMethodName()) {
case "convert": {
if (lambda.getImplMethodKind() == 5 && lambda.getFunctionalInterfaceClass().equals("LambdaSerializationTest$MyFunction") && lambda.getFunctionalInterfaceMethodName().equals("call") && lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") && lambda.getImplClass().equals("LambdaSerializationTest$AbstractConverter") && lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/String;")) {
return (MyFunction<String, String>)LambdaMetafactory.altMetafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;, convert(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)((ConverterA)((ConverterA)lambda.getCapturedArg(0)));
}
if (lambda.getImplMethodKind() == 5 && lambda.getFunctionalInterfaceClass().equals("LambdaSerializationTest$MyFunction") && lambda.getFunctionalInterfaceMethodName().equals("call") && lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") && lambda.getImplClass().equals("LambdaSerializationTest$AbstractConverter") && lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/String;")) {
return (MyFunction<String, String>)LambdaMetafactory.altMetafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;, convert(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)((ConverterB)((ConverterB)lambda.getCapturedArg(0)));
}
if (lambda.getImplMethodKind() != 5 || !lambda.getFunctionalInterfaceClass().equals("LambdaSerializationTest$MyFunction") || !lambda.getFunctionalInterfaceMethodName().equals("call") || !lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") || !lambda.getImplClass().equals("LambdaSerializationTest$AbstractConverter") || !lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/String;")) break;
return (MyFunction<String, String>)LambdaMetafactory.altMetafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;, convert(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)((ConverterC)((ConverterC)lambda.getCapturedArg(0)));
}
}
throw new IllegalArgumentException("Invalid lambda deserialization");
}
So it appears that actual captured argument is not used to determine which exact lambda has to be deserialized. All 3 lambdas will satisfy 1st if
condition and ConverterA
will be assumed.
When debugging we can observe that in runtime lambda.getCapturedArg(0)
is of a correct type (ConverterB
when exception is thrown) and also it worth noting that cast is not needed since method to be invoked is present in base AbstractConverter
class.
Is it expected behavior? If yes, what is recommended workaround?
Oracle has confirmed that it is a bug and assigned a following Bug ID: JDK-8161257
It is now visible on the official tracker: JDK-8161257
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With