然而,非捕获lambda似乎捕获了封闭的实例

Wil*_*son 13 java lambda language-lawyer java-8

我写了这段代码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我运行它,我会得到一个例外:

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)
Run Code Online (Sandbox Code Playgroud)

但是,我的理解是我正确地制作了lambda Serializable并且我声明它不会引用任何周围的上下文,因此它是一个非捕获的 lambda.然而,Main实例被捕获并且lambda表达式的结果无法序列​​化.我意识到我在lambda中声明了一个匿名类,但我希望lambda实例本身是它的封闭实例,而不是周围的Main类型.

这种行为是Java语言规范所期望的,如果是的话,为什么会这样?

Hol*_*ger 17

在lambda表达式的主体中,您有匿名类声明,new Thread() {}并且您不在static上下文中,因此该表达式隐式捕获this,在lambda表达式中具有与其外部相同的含义,根据JLS§15.27.2,Lambda Body:

与出现在匿名类的声明,名称的含义和代码this,并super在一个lambda身体出现关键词,与引用声明的可访问性一起,是同周围环境(除了拉姆达参数引入新的名称).

thislambda表达式的主体(包括显式和隐式)的透明度- 也就是说,将其视为与周围上下文相同 - 允许实现更灵活,并防止正文中非限定名称的含义依赖于超载分辨率.

由于周围的上下文决定了匿名类的行为,因此您可以通过使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,不会捕获周围的实例.

请注意,这可以被视为lambda表达式的一般概念,将函数定义为具有与它们所写的上下文完全相同的含义的表达式,除了函数参数的引入.生成功能接口的实例只是以兼容和安静有用的方式将这个概念引入Java编程语言的工具,而不是影响lambda表达式含义的概念.

  • @Eugene:OP已经明白`new Thread(){}`会捕获一个外部`this`实例,关键点是它将捕获的外部`this`是`Main`实例,它是一个lambda具体行为.所以`Thread`的子类将是`Main`的匿名内部类,而不是匿名`Supplier`实现.由于`new Thread(){}`需要一个'Main`实例来捕获,lambda表达式将是一个捕获lambda表达式. (5认同)
  • @William F. Jameson:在技术上和语义上,支持"break"和"continue"始终是一个挑战.但这并不是一个矛盾,因为"break"和"continue"没有不同的含义,它们根本就不受支持.确实是另一回事是`return`或`throw`.但请注意,这些是语句,而不是表达式,因此对于纯表达式,该图片可以正常工作. (4认同)
  • `表达式与它们所写的上下文中的含义完全相同 - 除了`return`,`break`和`continue`.作为一个例子,没有它们排除了使用lambdas进行循环的一些重要用例并限制了"forEach"的实用性. (3认同)
  • 每个lambda表达式都是一个纯表达式,用于评估功能接口的实例.我以为那是你的句子所指的表达.所以你实际上只是将lambda体作为表达式?但这仍然留下了概念上的差距.将它与Scala相比较,其中lambda的深度融合使得你甚至无法将它与"块"区分开来.Scala虽然没有'break`和`continue`键,但出于与此相同的原因. (2认同)