为什么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
返回StringBuilder
, s.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
方法所需的Consumer
function接口)。
这解释了为什么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.Stream
, forEach
的签名是:
void forEach(Consumer super T> 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
,这就是当您尝试显式返回值时出现编译错误的原因。