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

根据我对java 8 lambda表达式的理解,如果我们在花括号中不包含“ – >”之后的代码,那么将隐式返回值。 但是在下面的示例中, forEach方法期望Consumer和expression返回值,但编译器在Eclipse中没有给出错误。

 List 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

你的第四个例子 – messages.stream().forEach(s-> s); 不起作用的原因与以下方法不通过编译相同:

 void method (StringBuilder s) { s; } 

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

void forEach(Consumer action)

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

void accept(T t)

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

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

因为你不是想要return一些东西,所以lambda的返回类型是void ,它匹配所需的Consumer签名。

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

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

 void accept(Object o); 

即它不能返回任何东西。

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

你说

如果我们在花括号中的“ – >”之后不包含代码,那么将隐式返回值

但那不太准确。 相反,如果你不包括花括号,那么, 如果可以返回一个值,那么它将被隐式返回。 但是,如果不应返回值,因为,例如,函数接口方法具有void返回值,则编译器将看到该值,而不是尝试隐式返回任何内容。

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