为什么Java 8 Stream forEach方法的行为有所不同?

Mak*_*rat 8 java foreach lambda functional-programming java-8

根据我对java 8 lambda表达式的理解,如果我们在花括号中不包含" - >"之后的代码,那么将隐式返回该值.但是在下面的示例中,forEach方法expect Consumer和expression返回值,但编译器没有在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
Run Code Online (Sandbox Code Playgroud)

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

我在这里错过了什么?当我们在对象上调用方法时,为什么编译器没有给出错误?

Era*_*ran 12

来自JLS 15.27.3.Lambda表达式的类型:

如果满足以下所有条件,则lambda表达式与函数类型一致:

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

  • lambda参数的数量与函数类型的参数类型的数量相同.

  • 如果显式键入lambda表达式,则其形式参数类型与函数类型的参数类型相同.

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

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

    • 如果函数式的结果是一个(非空)类型R,然后或者i)所述拉姆达体是其中R兼容在赋值上下文,或ii)在lambda体是值兼容块,并且每个表达式结果表达式(第15.27.2节)与赋值上下文中的R兼容.

上面突出显示的句子意味着任何语句lambda表达式(即没有块的lambda表达式)匹配单个方法的返回类型所在voidConsumer功能接口(例如forEach方法所需的功能接口).

这解释了为什么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;});
Run Code Online (Sandbox Code Playgroud)

您的第4个示例 - messages.stream().forEach(s-> s);由于以下方法未通过编译而导致的原因不同:

void method (StringBuilder s)
{
    s;
}
Run Code Online (Sandbox Code Playgroud)

  • 可能有必要扩展`s`不起作用,因为`s`是一个*表达式*但不是*语句表达式*正如强调句子对`void`兼容的lambda表达式所要求的那样.*语句表达式*可以是方法调用(如示例所示)或赋值,例如`x - > foo = x`或递增/递减操作,例如`x - > count ++`.通过示例的直观解释,一切都可以作为语句写成`void method(StringBuilder s){statementExpression; }是正确的. (4认同)

Sto*_*ica 4

来自java.util.stream.Stream, 的签名forEach是:

void forEach(Consumer<? super T> action)

java.util.function.Consumeraction必须实现以下方法:

void accept(T t)

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

为什么当我们调用对象上的方法时编译器没有给出错误?

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

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