【问题标题】:Why Java 8 Stream forEach method behaves differently?为什么 Java 8 Stream forEach 方法的行为不同?
【发布时间】:2016-10-31 23:36:16
【问题描述】:

根据我对 java 8 lambda 表达式的理解,如果我们不在大括号中的“->”之后包含代码,则该值将被隐式返回。但在下面的例子中,forEach 方法需要 Consumer 并且表达式返回值,但编译器在 Eclipse 中没有给出错误。

List<StringBuilder> messages = Arrays.asList(new StringBuilder(), new StringBuilder());

messages.stream().forEach(s-> s.append("helloworld"));//works fine 

messages.stream().forEach((StringBuilder s)-> s.append("helloworld")); //works fine 

messages.stream().forEach(s-> s); // doesn't work , Void methods cannot return a value

messages.stream().forEach(s-> s.toString()); // works fine

messages.stream().forEach(s-> {return s.append("helloworld");}); // doesn't work , Void methods cannot return a value

messages.stream().forEach((StringBuilder s)-> {return s.append("helloworld");});  // doesn't work , Void methods cannot return a value

s.append 返回 StringBuilders.toString() 返回 String 但 lambda 将其视为 void

我在这里缺少什么?为什么我们在对象上调用方法时编译器没有报错?

【问题讨论】:

    标签: java foreach lambda functional-programming java-8


    【解决方案1】:

    来自JLS 15.27.3. Type of a Lambda Expression

    如果所有的 lambda 表达式都与函数类型一致 以下是正确的:

    • 函数类型没有类型参数。

    • lambda 参数个数与函数类型的参数类型个数相同。

    • 如果 lambda 表达式是显式类型的,其形参类型与函数类型的形参类型相同。

      • 如果假定 lambda 参数的类型与函数类型的参数类型相同,则:

      • 如果函数类型的结果为 void,则 lambda 主体是语句表达式(第 14.8 节)或 void 兼容块

      • 如果函数类型的结果是(非 void)类型 R,则 i) lambda 主体是与 R 兼容的表达式 在赋值上下文中,或者 ii) lambda 主体是值兼容的 块,并且每个结果表达式(第 15.27.2 节)都与 R 兼容 分配上下文。

    上面突出显示的那句话表示任何语句的lambda表达式(即没有块的lambda表达式)匹配单个方法返回类型为void的函数式接口(如forEach所需的Consumer函数式接口方法)。

    这解释了为什么 s.append("helloworld")s.toString()(您的 1,2 和 4 个示例)作为语句 lambda 表达式可以正常工作。

    示例 5 和 6 不起作用,因为它们具有块 lambda 主体,它们是值兼容的 lambda 表达式。要成为void-compatible,所有返回语句都必须不返回任何内容(即仅返回return;)。

    另一方面,以下void-compatible 块 lambda 体将通过编译:

    messages.stream().forEach(s-> {s.append("helloworld");});
    messages.stream().forEach(s-> {s.append("helloworld"); return;});
    

    您的第 4 个示例 - messages.stream().forEach(s-&gt; s); 不起作用,原因与以下方法未通过编译相同:

    void method (StringBuilder s)
    {
        s;
    }
    

    【讨论】:

    • 可能值得扩展 s 不起作用,因为 s表达式 而不是 语句表达式 void-由突出显示的句子兼容的 lambda 表达式。 statement expression 可以是方法调用(如示例中所示)或赋值,例如x -&gt; foo=x 或递增/递减操作,例如x -&gt; count++。通过示例的直观解释,您可以写成void method (StringBuilder s) { statementExpression; } 中的语句的所有内容都是正确的。
    【解决方案2】:

    来自java.util.stream.StreamforEach的签名是:

    void forEach(Consumer&lt;? super T&gt; action)

    java.util.function.Consumer 开始,action 必须实现以下方法:

    void accept(T t)

    在所有不起作用的示例中,您都返回了 T,它与 void 的返回类型不匹配。

    为什么当我们在对象上调用方法时编译器没有报错?

    因为您没有尝试 return 某事,所以 lambda 的返回类型是 void,它与所需的 Consumer 签名匹配。

    s -&gt; s 唯一可能的类型是T -&gt; T,而(StringBuilder s) -&gt; s.append() 可能是StringBuilder -&gt; (),它满足void 的要求。

    【讨论】:

    • 怎么样 **messages.stream().forEach(s-> s); ** 我没有明确返回任何东西,为什么编译器不认为它是无效的?
    • s 不能是语句,所以不能是void
    【解决方案3】:

    你说

    如果我们在大括号中“->”之后不包含代码,则该值将被隐式返回

    但这并不完全准确。相反,如果你不包含花括号,那么,if 一个值 could 被返回,那么它将被隐式返回。但是,如果不应该返回一个值,例如,因为函数式接口方​​法有一个 void 返回值,那么编译器将看到它并且不会尝试隐式返回任何内容。

    在这种情况下,forEach 接受 Consumer,其返回类型为 void,这就是当您尝试显式返回值时出现编译错误的原因。

    【讨论】:

    • messages.stream().forEach(s-> s); // 不起作用,void 方法不能返回值
    • @Makarand.Thorat s -&gt; s 唯一可能的类型是T -&gt; T
    【解决方案4】:

    Stream.forEach(Consumer) 接受实现一种方法的消费者

    void accept(Object o);
    

    即它不能返回任何东西。

    如果你想返回一些东西,你必须对返回的值做一些事情。

    【讨论】:

      猜你喜欢
      • 2017-04-13
      • 1970-01-01
      • 2016-09-19
      • 1970-01-01
      • 1970-01-01
      • 2017-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多