lambda 的序列化和反序列化

2022-09-03 15:34:27

下面的代码抛出

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 { }

原因似乎是在序列化和反序列化之后,fn1 和 fn2 最终成为同一个类。这是一个JDK/编译器错误,还是我错过了一些关于lambda的序列化和反序列化的东西?


答案 1

看看这个在2016年提出的开放JDK问题:

lambda 的反序列化导致 ClassCastException

它完全符合您的场景:

  • 两个(不同的)类,以及 ,它们都扩展了相同的基类 ,它有一个方法 。BCAString f()
  • 为类型对象创建对方法的引用 ;将此称为 []。Supplierf()Bbfnew B()::f
  • 为类型对象创建对方法的引用 ;cal this [].。Supplierf()Ccfnew C()::f
  • 序列 化cf (ObjectOutputStream#writeObject)
  • 当序列化被反序列化 () 时,将抛出 a,说类不能强制转换为类cfObjectInputStream#readObjectClassCastExceptionCB

关于这个问题有一个有趣的讨论,但丹·史密斯的最后一条评论似乎抓住了这一点:

对于这个特定的测试用例,重要的观察结果:方法引用的“限定类型”(即由字节码命名的类)应该与调用的限定类型相同:接收方的类型。javac 使用声明类的类型是错误的。请参见 JDK-8059632

修复了这个错误,我认为不同捕获类型的问题消失了。


答案 2

我不知道如何解释这一点,但行为是特别的,因为对于这两种情况,只用一次工作调用来执行主代码。serde()

那就是这个:

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

或这个 :

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

虽然两个调用是在同一个程序中执行的,但我注意到一个预期的事情:参数()的值对于这两种情况是不一样的。
但是我注意到一件意想不到的事情:在第二次调用时,返回的对象与第一次调用返回的引用相同。
就好像第一个被缓存并重复用于第二次调用一样。T tserde()ois.readObject()ois.readObject()

这似乎是一个错误。


推荐