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

use*_*730 28 java foreach lambda java-8

假设我们有一个Iterator<Integer> iterator.因为Iterable是一个功能界面,我们可以写:

Iterable<Integer> iterable = () -> iterator;
Run Code Online (Sandbox Code Playgroud)

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

for (Integer i : iterable) foo(i);
Run Code Online (Sandbox Code Playgroud)

那么为什么呢

for (Integer i : () -> iterator) foo(i);
Run Code Online (Sandbox Code Playgroud)

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

error: lambda expression not expected here
    for (Integer i : () -> iterator) foo(i);
                     ^
Run Code Online (Sandbox Code Playgroud)

使目标类型显式为

for (Integer i : (Iterable<Integer>) () -> iterator) foo(i);
Run Code Online (Sandbox Code Playgroud)

显然有效,但为什么编译器不能推断出λ-expression的目标类型?从表达式是λ表示法的事实来看,编译器不应该清楚目标类型不能是一个Array,因此必须是Iterable

这只是语言设计师的疏忽,还是我还缺少其他的东西?

Zho*_*gYu 16

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

有一点可以肯定的是,这不是一种疏忽; 案件被审议并被驳回.

引用早期规范:

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

决定允许哪些上下文支持多表达式在很大程度上取决于对这些功能的实际需求:

增强的for循环中的表达式不在多边形上下文中,因为,当构造当前被定义时,就好像表达式是接收器:( exp.iterator()或者,在数组的情况下exp[i]).有可能Iterator可以通过lambda表达式(for (String s : () -> stringIterator))包装为for循环中的Iterable ,但这与Iterable的语义不能很好地匹配.

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


在任何情况下,支持for-each循环中的目标类型似乎是不必要的工作.如果您已经拥有迭代器,那么您可以这样做

    iterator.forEachRemaining( element->{ ... } )
Run Code Online (Sandbox Code Playgroud)

或者如果你喜欢老派

    while(iterator.hasNext()) {
        Foo elment = iterator.next();
Run Code Online (Sandbox Code Playgroud)

也不是糟糕; 不值得使语言规范复杂化.(如果我们确实希望每个人都提供目标类型,请记住它也需要适用于其他多边形表达式,例如?:;然后for-each在某些情况下可能变得难以理解.通常,有两个可能的目标类型,Iterable<? extends X> | X[]这对于类型推理系统来说非常困难.)


for-each构造可以被认为是语法糖,因为lambda不可用.如果语言已经有lambda表达式,那么实际上没有必要使用特殊的语言结构来支持每个语言.它可以通过库API完成.

  • @user4235730:[这里](http://stackoverflow.com/q/839178/2711488)和[这里](http://stackoverflow.com/q/3883131/2711488)讨论了关于Iterable语义的观点。讨论比 lambda 表达式要古老得多…… (2认同)
  • @bayou.io:嗯,我对您在上次编辑之前编写的所有内容感到满意,他说语言设计者*不想*为 foreach 循环启用它。因此,类型推断的困难在这里无关紧要…… (2认同)
  • @Holger增强for循环的主要困难是在结肠的RHS表达类型已知之前无法进行分析; 但是如果该表达式是lambda,则在没有目标类型的情况下无法确定其类型,因为尚未进行for循环分析,所以目标类型尚不存在.当您观察到lambda不能产生数组时,所以"这似乎是合理的"这可以通过推测性地将Iterable建立为目标类型来完成.但总的来说,javac避免了推测型推理; 这种方式会导致疯狂. (2认同)
  • @Holger上面,结合使用lambda生成Iterable的可疑语义,将这个推到了优先级列表的极远.专门处理这个案例的额外努力,结合低利益,基本上解释了"语言设计者/编译器编写者不想这样做." 如果案件的价值被认为非常高,那么可能会发现某些方法可以使其发挥作用. (2认同)

dur*_*597 6

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

要确定lambda表达式的类型,Java编译器将使用发现lambda表达式的上下文或情境的目标类型.因此,您只能在Java编译器可以确定目标类型的情况下使用lambda表达式:

  • 变量声明

  • 分配

  • 退货声明

  • 数组初始化器

  • 方法或构造函数参数

  • Lambda表达体

  • 条件表达式,?:

  • 转换表达式

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

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

如果T是函数接口类型(第9.8节),并且表达式与从T派生的地面目标类型的函数类型一致,则lambda表达式在赋值上下文,调用上下文或具有目标类型T的强制转换上下文中是兼容的.

  • 赋值上下文 - 赋值上下文允许将表达式的值(第15.26节)赋值给变量.
  • 调用上下文 - 调用上下文允许方法或构造函数调用中的参数值
  • 转换上下文 - 转换上下文允许转换运算符(第15.16节)的操作数转换为转换运算符显式命名的类型.

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


至于"为什么"Java作者不允许这种情况,我不知道.说到Java编写者的想法通常超出了Stack Overflow的范围; 我试图解释为什么代码不起作用,但我无法猜测为什么他们选择以这种方式编写代码.

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

Lambda表达式和方法引用可能只出现在某些上下文中,它们的类型和正确性由此上下文决定.现有语言中的其他类型的表达式已经引入了对上下文的依赖性,这种趋势似乎可能会持续下去.而不是以特别方式处理每个新特征,聚合表达的引入和目标类型可以影响表达类型的显式识别允许我们统一处理单个伞下的依赖于上下文的表达.

......剪断

增强的for循环中的表达式不在多边形上下文中,因为,当构造当前被定义时,就好像表达式是接收器:( exp.iterator()或者,在数组的情况下exp[i]).有可能Iterator可以通过lambda表达式(for (String s : () -> stringIterator))包装为for循环中的Iterable ,但这与Iterable的语义不能很好地匹配.