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?

tetsuo
  • 10,726
  • 3
  • 32
  • 35
  • 6
    Side comment: you don't need the extra interface, you can simply write: `Runnable runnable = (Runnable & Serializable) () -> self.say();` – assylias May 23 '14 at 19:45
  • 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 :) – tetsuo May 23 '14 at 19:57
  • 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. – tetsuo May 23 '14 at 20:49
  • 1
    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. – Holger May 26 '14 at 09:00

2 Answers2

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.

tetsuo
  • 10,726
  • 3
  • 32
  • 35
  • Is this a case for deleting the question myself? – tetsuo May 23 '14 at 20:35
  • 3
    You should leave it in case somebody comes across the same issue. You should accept your own answer though. – assylias May 23 '14 at 21:15
  • 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. – Holger May 26 '14 at 09:13
  • @Holger Yes! Well observed. I've edited the answer to add the information. thx – tetsuo May 26 '14 at 12:12
  • I'm using Eclipse SDK Version: Luna SR2 (4.4.2) Build id: M20141210-0900 and this has been fixed. – Robert Bain Dec 15 '14 at 14:41
  • 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 @RobertBain – tetsuo Dec 15 '14 at 18:42
  • 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: http://stackoverflow.com/questions/27488567/invalid-lambda-deserialization – Robert Bain Dec 15 '14 at 19:38
  • I'm following these issues on Eclipse's tracker: [442416](https://bugs.eclipse.org/bugs/show_bug.cgi?id=442416), [442418](https://bugs.eclipse.org/bugs/show_bug.cgi?id=442418), and [449467](https://bugs.eclipse.org/bugs/show_bug.cgi?id=449467), all targeting 4.4.2/4.5. – tetsuo Dec 16 '14 at 00:08
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).

skiwi
  • 66,971
  • 31
  • 131
  • 216
  • 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. – Holger May 26 '14 at 09:09