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. Is this a JDK/compiler bug or am I missing something about serialization and deserialization of lambdas?
Serialization is a process for writing the state of an object into a byte stream so that we can transfer it over the network. We can serialize a lambda expression if its target type and its captured arguments have serialized.
You can serialize a lambda expression if its target type and its captured arguments are serializable. However, like inner classes, the serialization of lambda expressions is strongly discouraged.
Serialization is a mechanism of converting the state of an object into a byte stream. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory. This mechanism is used to persist the object. The byte stream created is platform independent.
Serialization is the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.
Have a look at this Open JDK issue raised back in 2016:
Deserialization of lambda causes ClassCastException
It quite precisely matches your scenario:
- Two (distinct) classes,
B
andC
, both of which extend the same base class,A
, which has a method,String f()
.- Create a
Supplier
reference to methodf()
for an object of typeB
; call thisbf
[new B()::f
].- Create a
Supplier
reference to methodf()
for an object of typeC
; cal thiscf
[new C()::f
].- Serialize
cf
(ObjectOutputStream#writeObject
)- When the serialized
cf
is deserialized (ObjectInputStream#readObject
), aClassCastException
is thrown saying that classC
cannot be cast to classB
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" (i.e., 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. See JDK-8059632.
Fix that bug, and I think the issue with different captured types goes away.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With