简体   繁体   English

lambda的序列化和反序列化

[英]Serialization and deserialization of lambda

This code below throws 以下代码抛出

Exception in thread "main" java.lang.ClassCastException: test.Subclass2 cannot be cast to test.Subclass1
at test.LambdaTest.main(LambdaTest.java:17)

public class LambdaTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ToLongFunction<B> fn1 = serde((ToLongFunction<B> & Serializable) B::value);
        ToLongFunction<C> fn2 = serde((ToLongFunction<C> & Serializable) C::value);
        fn1.applyAsLong(new B());
        fn2.applyAsLong(new C()); // Line 17 -- exception here!
    }

    private static <T extends Serializable> T serde(T t) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        new ObjectOutputStream(bos).writeObject(t);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos
                .toByteArray()));
        return (T) ois.readObject();
    }
}

class A {
    public long value() {
        return 0;
    }
}

class B extends A { }

class C extends A { }

The reason seems to be that after serialization and deserialization, both fn1 and fn2 end up as the same class. 原因似乎是在序列化和反序列化之后,fn1和fn2都以同一个类结束。 Is this a JDK/compiler bug or am I missing something about serialization and deserialization of lambdas? 这是一个JDK /编译器错误还是我错过了关于lambdas的序列化和反序列化的一些内容?

Have a look at this Open JDK issue raised back in 2016: 看看2016年提出的Open JDK问题:

Deserialization of lambda causes ClassCastException lambda的反序列化导致ClassCastException

It quite precisely matches your scenario: 它非常精确地匹配您的场景:

  • Two (distinct) classes, B and C , both of which extend the same base class, A , which has a method, String f() . 两个(不同的)类, BC ,它们都扩展了相同的基类A ,它有一个方法String f()
  • Create a Supplier reference to method f() for an object of type B ; 为类型B的对象创建方法f()Supplier引用; call this bf [ new B()::f ]. 叫这个bf [ new B()::f ]。
  • Create a Supplier reference to method f() for an object of type C ; 为类型为C的对象创建方法f()Supplier引用; cal this cf [ new C()::f ]. cal这个cf [ new C()::f ]。
  • Serialize cf ( ObjectOutputStream#writeObject ) 序列化cfObjectOutputStream#writeObject
  • When the serialized cf is deserialized ( ObjectInputStream#readObject ), a ClassCastException is thrown saying that class C cannot be cast to class B 当反序列化序列化cfObjectInputStream#readObject )时, ClassCastException ,说C类不能转换为B

There's an interesting discussion on the issue, but the very last comment by Dan Smith seems to nail it: 关于这个问题有一个有趣的讨论,但丹史密斯最后的评论似乎指出:

Important observation for this particular test case: the "qualifying type" (ie, the class named by the bytecode) of a method reference should be the same as the qualifying type of an invocation: the type of the receiver. 对此特定测试用例的重要观察:方法引用的“限定类型”(即字节码命名的类)应与调用的限定类型相同:接收器的类型。 javac is wrong to be using the type of the declaring class. javac使用声明类的类型是错误的。 See JDK-8059632 . JDK-8059632

Fix that bug, and I think the issue with different captured types goes away. 修复该错误,我认为不同捕获类型的问题消失了。

I don't know how to explain that but the behavior is particular because executing the main code with only one invocation of serde() works for both cases. 我不知道如何解释,但行为是特别的,因为执行主代码只有一个serde()调用适用于这两种情况。

That is this : 就是这样:

ToLongFunction<B> fn1 = serde((ToLongFunction<B> & Serializable) A::value);
fn1.applyAsLong(new B());

or this : 或这个 :

ToLongFunction<C> fn2 = serde((ToLongFunction<C> & Serializable) A::value);
fn2.applyAsLong(new C());

While as the two invocations are performed in the same program, I notice an expected thing : the value of the parameter ( T t ) is not the same for the two cases. 虽然两个调用是在同一个程序中执行的,但我注意到了一个预期的事情:参数( T t )的值对于这两种情况并不相同。
But I notice an unexpected thing : at the second invocation of serde() the object returned by ois.readObject() is the same reference as which one returned by the first ois.readObject() invocation. 但我注意到一个意想不到的事情:在第二次调用serde()返回的对象ois.readObject()是作为由第一返回的一个相同的附图ois.readObject()调用。
As if the first one was cached and reused for the second invocation. 好像第一个被缓存并重新用于第二次调用。

It seems a bug. 这似乎是一个错误。

If you want to make it work, then remove the default implementation of value() from BaseClass interface and override it in the implementing classes like this: 如果你想让它工作,那么从BaseClass接口中删除value()的默认实现,并在实现类中覆盖它,如下所示:

interface BaseClass {
    long value();
}
public class LambdaTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ToLongFunction<Subclass1> fn1 = serDe((Serializable & ToLongFunction<Subclass1>) Subclass1::value);
        fn1.applyAsLong(new Subclass1());
        ToLongFunction<Subclass2> fn2 = serDe((Serializable & ToLongFunction<Subclass2>) Subclass2::value);
        fn2.applyAsLong(new Subclass2());
    }

    private static <T extends Serializable> void printType(T t) {
        System.out.println(t.getClass().getSimpleName());
    }

    private static <T extends Serializable> T serDe(T t) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        new ObjectOutputStream(bos).writeObject(t);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        T readT = (T) ois.readObject();
        ois.close();
        bos.close();
        return readT;
    }
}
class Subclass1 implements BaseClass {

    @Override
    public long value() {
        return 0;
    }
}
class Subclass2 implements BaseClass {

    @Override
    public long value() {
        return 1;
    }
}

I hope it helps. 我希望它有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM