Java8 Lambda反序列化 - ClassCastException

Max*_*sev 7 java lambda serialization classcastexception java-8

ClassCastException Java8在满足以下条件时反序列化lambda时抛出:

  • 父类有一个方法,其引用用于自动创建Serializablelambda
  • 有几个子类可以扩展它,并且上面的方法有几种用法作为方法参考,但有不同的子类
  • 在使用方法引用之后,将序列化并反序列化
  • 所有方法引用都在同一个捕获类中使用

在Oracle Java编译器和运行时版本1.8.0_91上测试.请查找有关如何重现的测试代码:

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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

它给出了以下输出:

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
Run Code Online (Sandbox Code Playgroud)

$deserializeLambda$使用CFR 反编译此方法后,显示以下代码:

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");
}
Run Code Online (Sandbox Code Playgroud)

因此,似乎实际捕获的参数不用于确定哪个精确的lambda必须反序列化.所有3个lambdas将满足第一个if条件ConverterA并将被假设.

在调试时,我们可以观察到在运行时lambda.getCapturedArg(0)具有正确的类型(ConverterB抛出异常时),并且值得注意的是不需要强制转换,因为要调用的方法存在于基AbstractConverter类中.

这是预期的行为吗?如果是,推荐的解决方法是什么?

Hol*_*ger 4

Oracle 已确认这是一个错误,并分配了以下错误 ID:JDK-8161257

现在可以在官方跟踪器上看到:JDK-8161257