2016-07-06 9 views
6

ClassCastException von Java8 geworfen wird eine Lambda auf Deserialisieren wenn folgende Bedingungen erfüllt sind:Java8 Lambda Deserialisierung - Classcast

  • Geordnete Klasse eine Methode hat, deren Ausnutzung automatisch verwendet wird, eine Serializable Lambda
  • Es zu erstellen sind mehrere untergeordnete Klassen, die es erweitern, und es gibt mehrere Verwendungen der oben genannten Methode als eine Methodenreferenz, aber mit verschiedenen untergeordneten Klassen
  • Nachdem Methodenreferenz verbraucht wird, wird es serialisiert und deserialisiert
  • Alle Methodenreferenzen werden innerhalb der gleichen Erfassungsklasse verwendet

Getestet auf Oracle Java-Compiler und Laufzeitversionen 1.8.0_91. finden Sie Testcode auf, wie zu reproduzieren:

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

} 

Es gibt folgende Ausgabe:

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 

diese $deserializeLambda$ Methode mit CFR folgenden Code Nach decompiling enthüllt wird:

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

es also Es erscheint, dass das tatsächliche erfasste Argument nicht verwendet wird, um zu bestimmen, welches exakte Lambda deserialisiert werden muss. Alle 3 lambdas erfüllen den 1. if Zustand und ConverterA werden angenommen.

Beim Debuggen wir können das lambda.getCapturedArg(0) einer korrekten Art im laufenden Betrieb beobachten ist (ConverterB wenn Ausnahme ausgelöst wird) und lohnt sich auch, dass gegossene Feststellung nicht, da Verfahren benötigt wird, aufgerufen werden soll, die in Basis AbstractConverter Klasse.

Ist es erwartetes Verhalten? Wenn ja, was ist eine empfohlene Problemumgehung?

+0

Auf meinem PC funktioniert es wie ein Zauber mit Ausgang: 1,8. 0_60 test_A test_B test_C –

+0

Könnte es plattformspezifisch sein? Ich benutze Mac OS. –

+0

Ich benutze auch Mac :) –

Antwort

4

Oracle hat bestätigt, dass es ein Fehler ist und ein folgenden Fehler-ID zugewiesen: JDK-8161257

Es ist auf den offiziellen Tracker jetzt sichtbar ist: JDK-8161257