簡體   English   中英

Java8 Lambda反序列化 - ClassCastException

[英]Java8 Lambda Deserialization - ClassCastException

當滿足以下條件時,Java8在反序列化lambda時拋出ClassCastException

  • 父類有一個方法,其引用用於自動創建Serializable lambda
  • 有幾個子類可以擴展它,並且上面的方法有幾種用法作為方法參考,但有不同的子類
  • 在使用方法引用之后,將序列化並反序列化
  • 所有方法引用都在同一個捕獲類中使用

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

}

它給出了以下輸出:

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

在使用CFR反編譯此$deserializeLambda$方法后,將顯示以下代碼:

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

因此,似乎實際捕獲的參數不用於確定哪個精確的lambda必須反序列化。 if條件和ConverterA將被假設,所有3個lambdas將滿足1st。

在調試時,我們可以觀察到在運行時lambda.getCapturedArg(0)是一個正確的類型(拋出異常時的ConverterB ),並且值得注意的是不需要lambda.getCapturedArg(0) ConverterB ,因為要調用的方法存在於基本的AbstractConverter類中。

這是預期的行為嗎? 如果是,推薦的解決方法是什么?

Oracle已確認這是一個錯誤並分配了以下錯誤ID: JDK-8161257

它現在在官方跟蹤器上可見: JDK-8161257

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM