Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between stream().map() and stream.map({}) in java 8 [duplicate]

Tags:

java

java-8

Yesterday i stumbled over something i neither really understand nor i'm able to find an explanation:

Consider the following operations:

Stream.of(1, 2, 3).map(i -> i * 2).forEach(System.out::println);

//This one won't compile
Stream.of(1, 2, 3).map(i -> { i * 2; }).forEach(System.out::println);

It appears that the second one can be extended to

Stream.of(1, 2, 3).map(i -> { return i * 2;}).forEach(System.out::println);

and it will just compile fine. I came up with this because i'm kinda used to the first version but my IDE (Netbeans) always refers to the last version.

So my Question is: What is the difference/advantage of these two implementations? And why does the one with the {} block requires a return value? Where is the need for that value (except to make the code compile)?

Update:

With respect to When are braces optional in Java 8 lambda syntax?, this one can't be about lambda expression syntax only because

Stream.of(1, 2, 3).forEach(i -> { System.out.println(i); });

compiles fine, so is has to be(from my understading) about the implementation of map().

Cheers, Ben

like image 751
Ben Win Avatar asked Apr 17 '15 10:04

Ben Win


People also ask

What is the difference between the two types of streams in Java?

There are two basic types of stream defined by Java, called byte stream and character stream. The byte stream classes provide a convenient means for handling input and output of bytes and character streams provide a convenient means for handling input and output of characters, respectively.

What is the difference between findFirst () and findAny ()?

The findAny() method returns any element from a Stream, while the findFirst() method returns the first element in a Stream.

What are two types of streams in Java 8?

With Java 8, Collection interface has two methods to generate a Stream. stream() − Returns a sequential stream considering collection as its source. parallelStream() − Returns a parallel Stream considering collection as its source.


1 Answers

The difference is the following:

A lambda expression looks like

parameters -> expression

or

parameters -> { block }

where either the block returns a value - or it doesn't for a void-like behaviour.

In other words, a parameters -> expression lambda is equivalent to parameters -> { return expression; } if expression has a non-void type or to parameters -> { expression; } if expression has void type (such as System.out.printf()).

Your first version essentially uses an expression with a bit of overhead:

i -> i = i * 2 could be reduced to i -> i * 2, as the i = assignment is just unnecessary, because i disappears immediately afterwards without being used any further.

It is just like

Integer function(Integer i) {
    i = i * 2;
    return i;
}

or

Integer function(Integer i) {
    return (i = i * 2);
}

which can be simplified to

Integer function(Integer i) {
    return i * 2;
}

All these examples would match the interface UnaryOperator<Integer> which is a special case for Function<Integer, Integer>.

In contrast, you 2nd example is like

XY function(int i) {
    i = i * 2;
}

which doesn't work:

  • Either XY is void (which would make a Consumer<Integer> which doesn't fit with .map())
  • Or XY is indeed Integer (then the return statement is missing).

Where is the need for that value (except to make the code compile)?

Well, .forEach(System.out::println); needs that value...

So, everything which can be converted to a Function<T, R> can be given to a .map() of a T stream, resulting in an R stream:

Stream.of(1, 2, 3).map(i -> i * 2)
Stream.of(1, 2, 3).map(i -> { return i * 2; })

turn the Integer you give into another Integer, giving you another Stream<Integer>. You notice that they are boxed?

Other ways would be

// turn a Stream<Integer> to an IntStream with a 
// int applyAsInt(Integer i) aka ToIntFunction<Integer>
Stream.of(1, 2, 3).mapToInt(i -> i * 2)
Stream.of(1, 2, 3).mapToInt(i -> { return i * 2; })

// turn an IntStream to a different IntStream with a 
// int applyAsInt(int i) aka IntUnaryOperator
IntStream.of(1, 2, 3).map(i -> i * 2)
IntStream.of(1, 2, 3).map(i -> { return i * 2; })

// turn an IntStream to a Stream<Integer> with a 
// Integer apply(int i) aka IntFunction<Integer>
IntStream.of(1, 2, 3).mapToObj(i -> i * 2)
IntStream.of(1, 2, 3).mapToObj(i -> { return i * 2; })

All these examples have in common that they get a value and produce a value of either the same or a different type. (Note how these examples use AutoBoxing and AutoUnboxing as needed.)

OTOH, everything which can be converted to a Consumer<T> can be given to a .map() of a T stream, which can be any form of lambda which produces a void expression:

.forEach(x -> System.out.println(x))
.forEach(x -> { System.out.println(x); }) // no return as you cannot return a void expression
.forEach(System.out::println) // shorter syntax for the same thing
.forEach(x -> { }) // just swallow the value

With that in mind, it is easy to see that a lambda with void expression type cannot be given to .map(), and a lambda with a non-void type cannot be given to forEach().

like image 152
glglgl Avatar answered Sep 21 '22 10:09

glglgl