It seems to me it's a bug in the compiler or in the JVM, but maybe someone has a better explanation.
The following code runs fine as is, but if I uncomment the second runnable
initialization, that uses 'this' directly, it can't deserialize the object (in.readObject()
throws an exception).
public class TestClass implements Serializable {
String msg = "HEY!";
SerializableRunnable runnable;
public TestClass() {
TestClass self = this;
runnable = () -> self.say(); // uses a local copy of 'this'
// runnable = () -> this.say(); // uses 'this' directly
}
public void say() {
System.out.println(msg);
}
public static void main(String[] args) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(buffer)) {
out.writeObject(new TestClass());
}
try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()))) {
TestClass s = (TestClass) in.readObject();
s.say();
}
}
}
interface SerializableRunnable extends Runnable, Serializable {
}
This is the stacktrace for the root cause:
java.lang.IllegalArgumentException: Invalid lambda deserialization
at j8test.TestClass.$deserializeLambda$(TestClass.java:1)
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:483)
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:483)
at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1104)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1810)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1993)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1918)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at j8test.TestClass.main(TestClass.java:30)
Is it the expected behavior?
I tried everything but the most obvious.
The problem happens in Eclipse (wherein the java 8 support is still in beta), but not in javac. Thus, a JDT bug.
[EDIT]
I'm running:
Eclipse IDE for Java and Report Developers
Version: Luna RC1 Release (4.4.0RC1)
Build id: 20140522-1310
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
OS X 10.9.3
Maybe it's already corrected in a more recent build.
That is... rather odd.
This is what the documentation says about serializing lambdas:
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.
I am not fully familiar with the captured arguments, but I am assuming that it is referring to all the elements that are being captured by the lambda, meaning in this case that it refers this
, so it is a captured element then.
When further exploring that path, we see that TestClass
needs to be serializable, which it seems to be as it implements Serializable
. Moreover it will use the default lambda serialization (which is moreoften than not not a good idea), and it has as arguments a String
and a SerializableRunnable
, which both are Serializable
again.
So it seems to me that you've hit a bug in the JVM and it could be caused by the target being equal to a captured argument (this
).
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