Java 不捕获lambda似乎仍然捕获了封闭实例

Java 不捕获lambda似乎仍然捕获了封闭实例,java,lambda,java-8,language-lawyer,Java,Lambda,Java 8,Language Lawyer,我写了这段代码: 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 Excep

我写了这段代码:

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);
    }
}
import java.io.ObjectOutputStream;
导入java.io.Serializable;
导入java.util.function.Supplier;
公共班机{
公共静态void main(字符串[]args)引发异常{
新的Main();
}
private Main()引发异常{
Supplier=(Supplier&Serializable)(->new Thread(){};
新ObjectOutputStream(System.out).writeObject(供应商);
}
}
如果我运行它,我将得到一个异常:

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)
线程“main”中的异常java.io.NotSerializableException:main 位于java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) 位于java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378) 位于java.io.ObjectOutputStream.WriteObject 0(ObjectOutputStream.java:1174) 位于java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) 位于java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) 位于java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) 位于java.io.ObjectOutputStream.WriteObject 0(ObjectOutputStream.java:1178) 位于java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) 在Main(Main.java:28) 然而,我的理解是,我正确地将lambda
序列化,并声明它不引用任何周围的上下文,因此它是非捕获lambda。然而,
Main
实例被捕获,lambda表达式的结果无法序列化。我意识到我在lambda中声明了一个匿名类,但我希望lambda实例本身是它的封闭实例,而不是周围的
Main
类型


Java语言规范是否期望这种行为?如果是,原因是什么?

在lambda表达式的主体中,您有匿名类声明
new Thread(){}
,并且您不在
静态
上下文中,因此此表达式隐式捕获
this
,其在lambda表达式内的含义与在lambda表达式外的含义相同,根据:

与匿名类声明中出现的代码不同,lambda正文中出现的名称和
this
super
关键字的含义以及引用声明的可访问性与周围上下文中的含义相同(除了lambda参数引入新名称)

lambda表达式主体中的
this
(显式和隐式)的透明性(即,将其视为与周围上下文中相同的内容)允许实现更大的灵活性,并防止主体中非限定名称的含义依赖于重载解析

由于周围的上下文决定了匿名类的行为,因此可以通过使用
静态
上下文创建嵌套类来轻松解决此问题:

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);
    }
}
import java.io.ObjectOutputStream;
导入java.io.Serializable;
导入java.util.function.Supplier;
公共班机{
公共静态void main(字符串[]args)引发异常{
write();
}
静态void write()引发异常{
Supplier=(Supplier&Serializable)(->new Thread(){};
新ObjectOutputStream(System.out).writeObject(供应商);
}
}
然后,不会捕获周围的实例

请注意,这可以看作是lambda表达式的一般思想,将函数定义为与所编写的上下文中具有完全相同含义的表达式,但函数参数的引入除外。函数接口实例的生成只是以兼容和有用的方式将此概念引入Java编程语言的工具,而不是影响lambda表达式含义的概念。

简单地说:

  • 匿名类在非静态上下文中创建时始终引用封闭实例
  • lambda中的该
    是lambda的封闭实例(而不是lambda本身)
由此可知,lambda表达式中的匿名类捕获了包含lambda表达式的实例。在您的示例中,它捕获了不可序列化的
Main
实例

可能的解决办法:

  • 在静态上下文中创建lambda:在静态方法中或分配给静态字段
  • 将匿名类替换为
    静态
    嵌套类:
  • import java.io.ObjectOutputStream;
    导入java.io.Serializable;
    导入java.util.function.Supplier;
    公共班机{
    公共静态void main(字符串[]args)引发异常{
    新的Main();
    }
    private Main()引发异常{
    供应商=(供应商和可序列化)MyThread::new;
    新ObjectOutputStream(System.out).writeObject(供应商);
    }
    私有静态类MyThread扩展线程{
    }
    }
    
    因此,这与lambda的关系不如匿名内部类和它捕获的
    this
    表达式的意义与它们所编写的上下文中的含义完全相同
    ——除了
    返回
    中断
    继续
    。例如,没有它们就排除了使用lambdas循环的一些重要用例,并限制了
    forEach
    的实用性。@尤金:OP已经了解到
    new Thread(){}
    将捕获一个外部
    this
    实例,关键是它将捕获的外部
    this
    ,是
    Main
    实例,它是lambda特定的行为。因此
    线程
    的子类将是
    Main
    的匿名内部类,而不是匿名的
    供应商
    实现。由于
    new Thread(){}
    需要捕获
    Main
    实例,lambda表达式将是一个捕获lambda表达式。@William F。
    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 {
        }
    }