Java 8 lambdas that access instance fields and methods can't be deserialized
Asked Answered
W

2

15

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?

Wroth answered 23/5, 2014 at 19:38 Comment(4)
Side comment: you don't need the extra interface, you can simply write: Runnable runnable = (Runnable & Serializable) () -> self.say();Pluton
I know. I just wanted to make it clear the thing is Serializable, and it was not another 'how can I serialize lambdas' question. not clear enough, it seems :)Wroth
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.Wroth
Works for me (in Eclipse). Are you sure you run the most recent version? There were lambda serialization bugs in the past, I filed some of them to the Eclipse team by myself, but they were fixed quickly.Urethrectomy
W
6

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.

Wroth answered 23/5, 2014 at 20:35 Comment(8)
Is this a case for deleting the question myself?Wroth
You should leave it in case somebody comes across the same issue. You should accept your own answer though.Pluton
When talking about bugs in a software you should always add precise version information. With Kepler SR2 + Eclipse JDT/Java 8 support 1.0.0.v20140317-1956 your example works without problems.Urethrectomy
@Urethrectomy Yes! Well observed. I've edited the answer to add the information. thxWroth
I'm using Eclipse SDK Version: Luna SR2 (4.4.2) Build id: M20141210-0900 and this has been fixed.Headquarters
The fix is scheduled to SR2 indeed. It is planned to ship at the end of jan/15. A partial fix already shipped on an incremental update. Thanks for the update @RobertBainWroth
Thanks @tetsuo. I've been having further problems that aren't fixed by the maintenance fix. Do you have any insight as to whether they might be fixed in SR2? My problems are documented here: #27489067Headquarters
I'm following these issues on Eclipse's tracker: 442416, 442418, and 449467, all targeting 4.4.2/4.5.Wroth
A
3

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).

Avram answered 23/5, 2014 at 19:57 Comment(1)
I don’t get what you mean with your statement that using “default lambda serialization” is “moreoften than not a good idea”. There is no choice about how to serialize a lambda.Urethrectomy

© 2022 - 2024 — McMap. All rights reserved.