Let the code itself tell its story.
Here is your code, with additional printouts:
import java.util.*;
import java.util.stream.*;
public class StreamEvaluationOrder {
private static void p(String s) {
System.out.println(s);
}
public static void main(String[] args) {
List<Integer> li= Arrays.asList(1, 2, 3, null, 4, 5, null, 6, 7, 8);
li.stream()
.map(x -> { p("The element " + x + " is pulled from the list"); return x; })
.filter(e-> {
boolean res = e != null;
if (res) p(e + " is not null");
else p(e + " is null, therefore filtered out.\n");
return res;
})
.map(e-> {
int res = e * 2;
p(e + " is mapped to " + res);
return res;
})
.filter(e-> {
boolean res = e % 3 == 0;
if (res) p(e + " is divisible by three");
else p(e + " is not divisible by three, therefore filtered out. \n");
return res;
})
.forEach(e-> {
p(e + " reaches the forEach.\n");
});
}
}
Here is the story that it tells you:
The element 1 is pulled from the list
1 is not null
1 is mapped to 2
2 is not divisible by three, therefore filtered out.
The element 2 is pulled from the list
2 is not null
2 is mapped to 4
4 is not divisible by three, therefore filtered out.
The element 3 is pulled from the list
3 is not null
3 is mapped to 6
6 is divisible by three
6 reaches the forEach.
The element null is pulled from the list
null is null, therefore filtered out.
The element 4 is pulled from the list
4 is not null
4 is mapped to 8
8 is not divisible by three, therefore filtered out.
The element 5 is pulled from the list
5 is not null
5 is mapped to 10
10 is not divisible by three, therefore filtered out.
The element null is pulled from the list
null is null, therefore filtered out.
The element 6 is pulled from the list
6 is not null
6 is mapped to 12
12 is divisible by three
12 reaches the forEach.
The element 7 is pulled from the list
7 is not null
7 is mapped to 14
14 is not divisible by three, therefore filtered out.
The element 8 is pulled from the list
8 is not null
8 is mapped to 16
16 is not divisible by three, therefore filtered out.
The principle is that it pulls the elements from the source stream one by one, pushes each element through the entire pipeline, and only then begins to process the next element. Is it clearer now?