Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loop fusion of Stream in Java-8 (how it works internally)

I'm reading the book 'Java in Action'.

And I saw an example code of Stream in the book.

List<String> names = menu.stream()
                         .filter(d -> {
                                       System.out.println("filtering" + d.getName());
                                       return d.getCalories() > 300;
                                      })
                         .map(d -> {
                                    System.out.println("mapping" + d.getName());
                                    return d.getName();
                                    })
                         .limit(3)
                         .collect(toList());

When the code is executed, the result is as follows.

filtering __1__.
mapping __1__.
filtering __2__.
mapping __2__.
filtering __3__.
mapping __3__.

That is, because of limit(3), the log message is printed only 3 times! In this book, this is called in "loop fusion." But, I don't understand this. Because, if you know whether an object is filtered, you have to calculate the filtering function. Then, "filtering ..." message is should be printed, I think.

Please, Explain me about how the loop fusion works internally.

like image 682
Sohyun Ahn Avatar asked Mar 15 '17 08:03

Sohyun Ahn


1 Answers

“Because, if you [want to] know whether an object is filtered, you have to calculate the filtering function”, is right, but perhaps your sample data wasn’t sufficient to illustrate the point. If you try

List<String> result = Stream.of("java", "streams", "are", "great", "stuff")
    .filter(s -> {
                  System.out.println("filtering " + s);
                  return s.length()>=4;
                 })
    .map(s -> {
               System.out.println("mapping " + s);
               return s.toUpperCase();
              })
    .limit(3)
    .collect(Collectors.toList());
System.out.println("Result:");
result.forEach(System.out::println);

it will print

filtering java
mapping java
filtering streams
mapping streams
filtering are
filtering great
mapping great
Result:
JAVA
STREAMS
GREAT

Showing that

  • In order to find three elements matching the filter, you might have to evaluate more than three elements, here, four element are evaluated, but you don’t need to evaluate subsequent elements once you have three matches

  • The subsequent mapping function only need to be applied to matching elements. This allows to conclude that it is irrelevant whether .map(…).limit(…)or .limit(…).map(…) was specified.
    This differs from the relative position of .filter and .limit which is relevant.

The term “loop fusion” implies that there is not a filtering loop, followed by a mapping loop, followed by a limit operation, but only one loop (conceptionally), performing the entire work, equivalent to the following single loop:

String[] source = { "java", "streams", "are", "great", "stuff"};
List<String> result = new ArrayList<>();
int limit = 3;
for(String s: source) {
    System.out.println("filtering " + s);
    if(s.length()>=4) {
        System.out.println("mapping " + s);
        String t = s.toUpperCase();
        if(limit-->0) {
            result.add(t);
        }
        else break;
    }
}
like image 183
Holger Avatar answered Nov 04 '22 11:11

Holger