在Java中,为什么我不能使用lambda作为增强的for循环表达式?

2022-09-01 12:12:47

假设我们有一个 .因为是功能接口,我们可以这样写:Iterator<Integer> iteratorIterable

Iterable<Integer> iterable = () -> iterator;

然后,我们当然可以用作增强的 for 循环表达式:iterable

for (Integer i : iterable) foo(i);

那么为什么是

for (Integer i : () -> iterator) foo(i);

不允许?(这会导致以下编译器错误:)

error: lambda expression not expected here
    for (Integer i : () -> iterator) foo(i);
                     ^

使目标类型显式化,例如

for (Integer i : (Iterable<Integer>) () -> iterator) foo(i);

显然有效,但是如果省略λ表达式的目标类型,为什么编译器不能推断出它呢?从表达式采用 λ 表示法的事实来看,编译器是否应不清楚目标类型不能为 ,因此必须是 ?ArrayIterable

这只是语言设计师的疏忽,还是我在这里错过了其他东西?


答案 1

这不仅仅是关于lambda表达式;它是关于所有需要目标类型的多边形表达式。

有一件事是肯定的,这不是一个疏忽;该案被审议并被驳回。

引用早期规范:

http://cr.openjdk.java.net/~dlsmith/jsr335-0.9.3/D.html

决定允许哪些上下文支持 poly 表达式在很大程度上是由对这些要素的实际需求驱动的:

增强型 for 循环中的表达式不在 poly 上下文中,因为正如当前定义的构造一样,表达式就好像是接收器:(或者,在数组情况下,)。迭代器可以通过 lambda 表达式 () 包装为 for 循环中的迭代器,这是有道理的,但这与 Iterable 的语义不能很好地融合。exp.iterator()exp[i]for (String s : () -> stringIterator)

我的看法是,每次调用 都必须返回一个新的、独立的迭代器,该迭代器位于开头。但是,示例(以及您的示例中)中的 lambda 表达式返回相同的迭代器。这不符合 的语义。Iterable.iterator()Iterable


无论如何,支持目标键入 for-each 循环似乎没有必要。如果您已经拥有迭代器,则可以简单地执行

    iterator.forEachRemaining( element->{ ... } )

或者,如果您更喜欢老派

    while(iterator.hasNext()) {
        Foo elment = iterator.next();

两者都不太糟糕。不值得使语言规范更加复杂化。(如果我们确实希望 for-each 提供目标类型,请记住它也需要适用于其他 poly 表达式,例如 ;那么在某些情况下,for-each 可能会变得太难理解。一般来说,有两种可能的目标类型,这对于类型推理系统来说是非常困难的。?:Iterable<? extends X> | X[]


for-each 构造可以被视为语法糖,因为 lambda 不可用。如果语言已经具有lambda表达式,则确实没有必要使用特殊的语言构造来支持每个表达式;它可以通过库 API 来完成。


答案 2

Lambda 表达式文档中,它们列出了可以推断目标类型的方案,具体而言:

为了确定 lambda 表达式的类型,Java 编译器使用找到 lambda 表达式的上下文或情况的目标类型。因此,您只能在 Java 编译器可以确定目标类型的情况下使用 lambda 表达式:

  • 变量声明

  • 作业

  • 返回语句

  • 数组初始值设定项

  • 方法或构造函数参数

  • Lambda 表达式正文

  • 条件表达式 ?:

  • 强制转换表达式

此方案不是这些方案,因此无法推断目标类型。我同意你的观点,目标类型永远不可能是数组(因为数组不是功能接口),但文档很清楚这不是这些方案之一。

具体来说,在JLS 15.27.3中,它说:

如果 Lambda 表达式与目标类型 T 的赋值上下文、调用上下文或强制转换上下文兼容,前提是 T 是函数接口类型 (§9.8),并且表达式与从 T 派生的地面目标类型的函数类型一致。

  • 赋值上下文 - 赋值上下文允许将表达式的值 (§15.26) 赋值分配给变量。
  • 调用上下文 - 调用上下文允许在方法或构造函数调用中使用参数值
  • 转换上下文 - 转换上下文允许将强制转换运算符 (§15.16) 的操作数转换为由强制转换运算符显式命名的类型。

显然,这些都不是。唯一可能的是调用上下文,但增强的循环构造不是方法调用。for


至于Java作者不允许“为什么”这种情况,我不知道。与Java编写者交谈通常超出了Stack Overflow的范围;我试图解释为什么代码不起作用,但我无法猜测为什么他们选择这样写。

附录,@bayou.io的答案中的解释/讨论仍然存在于JSR-335的最终版本中

Lambda 表达式和方法引用可能仅出现在某些上下文中,其类型和正确性由此上下文决定。现有语言中的其他类型的表达式已经引入了对上下文的依赖性,这种趋势似乎可能会继续下去。多边形表达式的引入和目标类型可以影响表达式类型的显式识别,不是以临时方式处理每个新功能,这使我们能够在单个保护伞下统一处理上下文相关的表达式。

...剪 断

增强型 for 循环中的表达式不在 poly 上下文中,因为正如当前定义的构造一样,表达式就好像是接收器:(或者,在数组情况下,)。迭代器可以通过 lambda 表达式 () 包装为 for 循环中的迭代器,这是有道理的,但这与 Iterable 的语义不能很好地融合。exp.iterator()exp[i]for (String s : () -> stringIterator)