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