Java 8 lambdas, которые не могут десериализовать поля и методы экземпляра доступа

Мне кажется, что это ошибка в компиляторе или в JVM, но, возможно, у кого-то есть лучшее объяснение.

Следующий код работает нормально, как есть, но если я раскомментирую вторую инициализацию runnable, которая напрямую использует 'this', она не может десериализовать объект (in.readObject() выдает исключение).

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 {
}

Это значение stacktrace для основной причины:

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)

Это ожидаемое поведение?

Ответы

Ответ 1

Я пробовал все, кроме самого очевидного.

Проблема возникает в Eclipse (где поддержка java 8 все еще находится в состоянии бета-тестирования), но не в javac. Таким образом, ошибка JDT.

[EDIT]

Я запускаю:

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

Возможно, он уже исправлен в более поздней версии.

Ответ 2

Это... довольно странно.

Это то, что документация говорит о сериализации lambdas:

Вы можете сериализовать выражение лямбда, если его целевой тип и его захваченные аргументы являются сериализуемыми. Однако, как и внутренние классы, сериализация лямбда-выражений категорически не рекомендуется.

Я не полностью ознакомлен с захваченными аргументами, но я предполагаю, что он относится ко всем элементам, которые захватываются лямбда, что означает в этом случае, что оно относится к this, поэтому это захваченный элемент затем.

При дальнейшем изучении этого пути мы видим, что TestClass должен быть сериализуемым, что похоже на то, что он реализует Serializable. Более того, он будет использовать стандартную лямбда-сериализацию (что более часто, чем не очень хорошая идея), и оно имеет в качестве аргументов a String и a SerializableRunnable, которые оба являются Serializable снова.

Итак, мне кажется, что вы попали в ошибку в JVM, и это может быть вызвано тем, что цель равна захваченному аргументу (this).