Нехватывающая лямбда, тем не менее, захватывает охватывающий экземпляр
Я написал этот код:
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) throws Exception {
new Main();
}
private Main() throws Exception {
Supplier<Thread> supplier = (Supplier<Thread> & Serializable) () -> new Thread() {};
new ObjectOutputStream(System.out).writeObject(supplier);
}
}
Если я запустил его, я получу исключение:
Exception in thread "main" java.io.NotSerializableException: Main
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at Main.<init>(Main.java:28)
Тем не менее, я понимаю, что я правильно сделал лямбда Serializable
, и я объявил это так, что он не относится к окружающему контексту, поэтому он не захватывает лямбда. Тем не менее экземпляр Main
фиксируется, и результат выражения лямбда не может быть сериализован. Я понимаю, что объявляю анонимный класс внутри лямбда, но я ожидал бы, что сам экземпляр лямбда будет его охватывающим экземпляром, а не окружающим типом Main
.
Является ли это поведение ожидаемым спецификацией языка Java и, если да, то как?
Ответы
Ответ 1
В вашем элементе лямбда-выражения у вас есть объявление анонимного класса new Thread() {}
, и вы не находитесь в контексте static
, поэтому это выражение неявно захватывает this
, которое имеет такое же значение в выражении лямбда, что и вне it, per JLS §15.27.2, Lambda Body:
В отличие от кода, появляющегося в объявлениях анонимного класса, значение имен и ключевых слов this
и super
, входящих в тело лямбда, наряду с доступностью ссылочных объявлений, такое же, как в окружающем контексте (за исключением того, что параметры лямбда вводят новые имена).
Прозрачность this
(как явная, так и неявная) в теле выражения лямбда - то есть, рассматривая его так же, как и в окружающем контексте, - обеспечивает большую гибкость для реализаций и предотвращает значение неквалифицированных имен в тело зависит от разрешения перегрузки.
Поскольку окружающий контекст определяет поведение анонимного класса, вы можете легко исправить проблему, используя контекст static
для создания вложенного класса:
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) throws Exception {
write();
}
static void write() throws Exception {
Supplier<Thread> supplier = (Supplier<Thread> & Serializable)() -> new Thread() {};
new ObjectOutputStream(System.out).writeObject(supplier);
}
}
Тогда никакой окружающий экземпляр не будет захвачен.
Обратите внимание, что это можно рассматривать как общую идею лямбда-выражений, чтобы определить функции как выражения, имеющие то же значение, что и в контексте, который они пишут, за исключением введения параметров функции. Генерация экземпляра функционального интерфейса - это всего лишь средство, позволяющее получить эту концепцию на языке программирования Java совместимым и бесшумным способом, но не концепцией влияния на значение лямбда-выражения.
Ответ 2
Проще говоря:
- анонимные классы всегда ссылаются на экземпляр экземпляра при создании в нестационарном контексте
-
this
в lambda - экземпляр, охватывающий лямбда (не сам лямбда)
Из этого следует, что анонимный класс внутри выражения лямбда захватывает экземпляр, охватывающий выражение лямбда. В вашем примере он захватывает экземпляр Main
, который не является сериализуемым.
Возможные решения:
- создать лямбда в статическом контексте: в статическом методе или назначить статическое поле
- заменить анонимный класс на
static
вложенный класс:
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) throws Exception {
new Main();
}
private Main() throws Exception {
Supplier<Thread> supplier = (Supplier<Thread> & Serializable) MyThread::new;
new ObjectOutputStream(System.out).writeObject(supplier);
}
private static class MyThread extends Thread {
}
}