Makarand.Thorat
Makarand.Thorat

Reputation: 267

Why Java 8 Stream forEach method behaves differently?

As per my understanding of java 8 lambda expressions, if we don't include code after "->" in curly braces that value will be returned implicitly. But in case of below example, forEach method expects Consumer and expression returns value but the compiler is not giving an error in 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 returns StringBuilder and s.toString() returns String but lambda treats it as void.

What am I missing here? Why isn't the compiler giving an error when we invoke method on object?

Upvotes: 10

Views: 5045

Answers (4)

Eran
Eran

Reputation: 393771

From JLS 15.27.3. Type of a Lambda Expression:

A lambda expression is congruent with a function type if all of the following are true:

  • The function type has no type parameters.

  • The number of lambda parameters is the same as the number of parameter types of the function type.

  • If the lambda expression is explicitly typed, its formal parameter types are the same as the parameter types of the function type.

    • If the lambda parameters are assumed to have the same types as the function type's parameter types, then:

    • If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.

    • If the function type's result is a (non-void) type R, then either i) the lambda body is an expression that is compatible with R in an assignment context, or ii) the lambda body is a value-compatible block, and each result expression (§15.27.2) is compatible with R in an assignment context.

The highlighted sentence above means that any statement lambda expression (i.e. a lambda expression without a block) matches a functional interface whose single method's return type is void (such as the Consumer functional interface required by the forEach method).

This explains why s.append("helloworld") & s.toString() (your 1,2 & 4 examples) work fine as statement lambda expressions.

examples 5 & 6 don't work, since those have block lambda bodies which are value-compatible lambda expressions. To be void-compatible, all the return statements must return nothing (i.e. just return;).

On the other hand, the following void-compatible block lambda bodies will pass compilation :

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

Your 4th example - messages.stream().forEach(s-> s); doesn't work for the same reason the following method doesn't pass compilation :

void method (StringBuilder s)
{
    s;
}

Upvotes: 13

OrangeDog
OrangeDog

Reputation: 38749

From java.util.stream.Stream, the signature of forEach is:

void forEach(Consumer<? super T> action)

From java.util.function.Consumer, the action must implement the following method:

void accept(T t)

In all your examples that don't work, you're returning a T, which doesn't match the return type of void.

Why compiler is not giving error when we invoke method on object?

Because you're not trying to return something, the lambda's return type is void, which matches the required Consumer signature.

The only possible type for s -> s is T -> T, whereas (StringBuilder s) -> s.append() could be StringBuilder -> (), which satisfies the void requirement.

Upvotes: 4

ipsi
ipsi

Reputation: 2070

You said

if we don't include code after "->" in curly braces that value will be returned implicitly

But that's not quite accurate. Rather, if you do not include curly braces, then, if a value could be be returned, then it will be returned implicitly. However, if a value should not be returned, because, for example, the functional interface method has a void return value, then the compiler will see that and not attempt to implicitly return anything.

In this case, forEach accepts a Consumer, whose return type is void, which is why you are getting compile errors when you attempt to return a value from it explicitly.

Upvotes: 1

Peter Lawrey
Peter Lawrey

Reputation: 533432

Stream.forEach(Consumer) takes a consumer which implements one method

void accept(Object o);

i.e. it can't return anything.

If you want to return something you have to do something with the value returned.

Upvotes: 1

Related Questions