Lambda Java Descript - ClassCastException

ClassCastException is thrown by Java8 when lambda is deserialized under the following conditions:

  • The parent class has a method that is referenced to automatically create a Serializable lambda
  • There are several child classes that extend it, and there are several ways to use the above method as a method reference, but with different child classes
  • After using the method, the link is serialized and deserialized.
  • All method references are used in the same capture class.

Tested with Oracle Java compiler and version 1.8.0_91. Please find a 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); } } } 

He gives the following conclusion:

 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 

After decompiling this method $deserializeLambda$ with CFR, the following code will appear:

 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 seems that the actual captured argument is not used to determine which lambda should be deserialized. All 3 lambdas will satisfy the 1st if condition, and ConverterA will be accepted.

During debugging, we can notice that at runtime lambda.getCapturedArg(0) is of the correct type ( ConverterB when an exception is thrown), and it is worth noting that the cast is not required, since the method to be called is present in the AbstractConverter base class.

Is behavior expected? If so, what is the recommended workaround?

+6
source share
1 answer

Oracle confirmed that this is an error and assigned the following error identifier: JDK-8161257

Now it can be seen on the official tracker: JDK-8161257

+4
source

All Articles