Reputation: 8885
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?
Upvotes: 15
Views: 1122
Reputation: 608
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:
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;
}
}
Upvotes: 0
Reputation: 200236
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.
Upvotes: 9
Reputation: 131496
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.
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.
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.
As if the first one was cached and reused for the second invocation.
It seems a bug.
Upvotes: 0