【问题标题】:In Java, why can't I use a lambda as an enhanced for loop's Expression?在 Java 中,为什么我不能使用 lambda 作为增强的 for 循环表达式?
【发布时间】:2015-12-07 00:39:01
【问题描述】:

假设我们有一个Iterator<Integer> iterator。因为Iterable是函数式接口,我们可以这样写:

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

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

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);

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

这只是语言设计者的疏忽,还是我在这里遗漏了什么?

【问题讨论】:

  • 我没有看到 Iterable 标记为 @FunctionalInterface
  • @Farrandu 不需要将其标记为 FunctionalInterface 即可成为功能接口
  • @SleimanJneidi 哎呀...你是对的,不知道
  • @Farrandu 不必如此。 JLS 9.8 说 A functional interface is an interface that has just one abstract method (aside from the methods of Object), and thus represents a single function contract. @FunctionalInterface 澄清它是用作功能接口的,如果不是,则是编译时错误。
  • 它没有被标记为@FunctionalInterface,因为它并不是特别打算以这种方式使用。

标签: java foreach lambda java-8


【解决方案1】:

作为durron597 explains,您只能在 Java 编译器可以确定目标类型的情况下使用 lambda 表达式。

在 for-each 循环中:

for (FormalParameter : Expression) 语句

JLS says那个:

Expression 的类型必须是 Iterable 或数组类型(§10.1), 或发生编译时错误。

这意味着允许使用两种不同的类型,而不是 lambda 表达式所需的一种,因此无法以 lambda 表达式在 for-each 循环中工作的方式设计语言。没有使用 lambda 表达式的概念。

当您添加强制转换时,只允许使用一种类型,因此编译器可以确定 lambda 表达式的目标类型。这就是“使目标类型明确”有效的原因。

【讨论】:

  • “如果你有一个数组而不是一个迭代器,并且使用一个 lambda 表达式并转换为正确的数组类型,它也可以工作。” ...你能举个例子吗?
  • @JoseAntonioDuraOlmos 为什么你说“没有办法做到这一点”,当 JSR-335 的作者说它是“合理的”时,它本可以这样做,但它只是没有t 符合 Iterables 语义,正如它在我的 anbayou.io 的两个答案中所说的那样?
  • 当前的 lambda 表达式需要在需要一个功能接口的上下文中。这发生在分配、调用和强制转换中。在 for-each 的表达式中,期望有两个候选者,一个 Iterable(它恰好是一个功能接口)或一个 char[]。使用稍微不同的 lambda 表达式是可能的,当允许多个预期类型但其中只有一个是功能接口时允许使用。所以,是的,它可以用不同的 lambda 概念来完成,但不能用当前的概念来完成。他们可能觉得额外的复杂性不值得。
  • 和 Iterable 或我的意思是一个数组,任何数组。不必是 char[]
  • 那不是你说的。你说“没办法”,你没有说“额外的复杂性不值得”。
【解决方案2】:

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

可以肯定的是,这不是疏忽。该案被考虑并驳回。

引用早期规范:

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

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

增强型 for 循环中的表达式不在 poly 上下文中,因为根据当前定义的构造,它就好像表达式是一个接收器:exp.iterator()(或者,在数组的情况下,exp[i]) . Iterator 可以通过 lambda 表达式 (for (String s : () -&gt; stringIterator)) 在 for 循环中包装为 Iterable 是合理的,但这与 Iterable 的语义不太吻合。

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


无论如何,在 for-each 循环中支持目标输入似乎是不必要的工作。如果你已经有了迭代器,你可以简单地做

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

或者如果你更喜欢老派

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

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


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

【讨论】:

  • 是的,该草案规范比挖掘 JLS 更具可读性:)
  • @user4235730:关于 Iterable 语义的要点在here 和here 进行了讨论。讨论比 lambda 表达式要古老得多……
  • @bayou.io:嗯,我对你在上次编辑之前写的所有内容都很满意,说语言设计者不想要为foreach 循环。因此,类型推断的困难在这里无关紧要……
  • @Holger 增强型for循环的主要困难在于,在知道冒号右轴上的表达式类型之前无法对其进行分析;但如果该表达式是一个 lambda,则如果没有目标类型,则无法确定其类型,因为尚未完成 for 循环分析,因此该目标类型尚不存在。当您观察到 lambda 不能生成数组时,因此“这很合理”可以通过推测性地将 Iterable 建立为目标类型来完成。但总的来说 javac 避免了推测类型推断。那样会导致疯狂。
  • @Holger 上面的内容,再加上让 lambda 产生一个 Iterable 的可疑语义,把它推到了优先级列表的后面。专门处理这种单一情况的额外努力,加上低收益,基本上解释了“语言设计者/编译器作者不想这样做”。如果认为 case 的价值非常高,那么很可能会找到某种方法使其发挥作用。
【解决方案3】:

在Lambda Expressions documentation中,他们列出了可以推断目标类型的场景,具体如下:

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

  • 变量声明

  • 作业

  • 返回语句

  • 数组初始化器

  • 方法或构造函数参数

  • Lambda 表达式体

  • 条件表达式,?:

  • 演员表

这个场景不是这些,所以无法推断目标类型。我同意你的观点,目标类型永远不能是数组(因为数组不是函数式接口),但文档清楚地表明这不是这些场景之一。

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

如果 T 是函数式接口类型(第 9.8 节)并且表达式与派生的基本目标类型的函数类型一致,则 lambda 表达式在赋值上下文、调用上下文或转换上下文中与目标类型 T 兼容来自T。

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

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


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

附录,@bayou.io 的答案中的解释/讨论仍然存在于final version of JSR-335:

Lambda 表达式和方法引用可能只出现在某些上下文中,它们的类型和正确性由该上下文决定。现有语言中的其他类型的表达式已经引入了对上下文的依赖,而且这种趋势似乎可能会持续下去。与其以特别的方式处理每个新特征,不如引入多表达式并明确认识到目标类型可以影响表达式类型,这使我们能够在一个单一的保护伞下统一处理与上下文相关的表达式。

...剪辑

增强型 for 循环中的表达式在 poly 上下文中是 not,因为根据当前定义的构造,就好像表达式是接收器:exp.iterator()(或者,在数组案例,exp[i])。 Iterator 可以通过 lambda 表达式 (for (String s : () -&gt; stringIterator)) 在 for 循环中包装为 Iterable 是合理的,但这与 Iterable 的语义不太吻合。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-15
    • 1970-01-01
    • 1970-01-01
    • 2013-08-03
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多