Stream API received a few nice additions with the Java 9. Both Stream interfaces as well as Collectors were updated. Let's start with the new collectors. To show these improvements we will use next classes:
public class Student {
private String firstName;
private String lastName;
private Long averageMark;
// ...
}
public class Group {
private String name;
private Set<Student> students = new HashSet<>();
// ...
}

Flatmapping and filtering collectors

Two new collectors were added to Collectors. Both of them are intended to be used for multilevel reduction, mostly for downstream collecting with Collectors#groupingBy() and Collectors#partitioningBy(). Two collectors that we want to look at are:

  • Collectors#filtering() - receives a filtering predicate and a downstream collector. Applies a filter and collects the filtered items with a provided collector.
  • Collectors#flatMapping() - receives a mapper and a downstream collector. Applies a flat mapping operation, using the mapper and then collects the mapped items with a provided collector.

Their names are pretty descriptive about what they do. Here are a few examples using these new collectors as well as the code without them but which will produce the same results:
/**
* Find studnets that are in groups that have more than 2 studnets and
* the ones who are in other groups. This can be achived by applying
* partitioning operation.
*/
public void newCollectorsForPartitioning() throws Exception {
// Java 9 new filtering and flatMapping collectors
Map<Boolean, List<Student>> map = getGroupStream()
.collect(
Collectors.partitioningBy(
group -> group.getStudents().size() > 2,
Collectors.flatMapping(
group -> group.getStudents().stream(),
Collectors.toList()
)
)
);
assertThat( map ).hasSize( 2 );
assertThat( map.get( Boolean.FALSE ) ).hasSize( 2 );
assertThat( map.get( Boolean.TRUE ) ).hasSize( 3 );
// Using Java 8 collectors
map = getGroupStream()
.collect(
Collectors.partitioningBy(
group -> group.getStudents().size() > 2,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.flatMap( g -> g.getStudents().stream() )
.collect( Collectors.toList() )
)
)
);
assertThat( map ).hasSize( 2 );
assertThat( map.get( Boolean.FALSE ) ).hasSize( 2 );
assertThat( map.get( Boolean.TRUE ) ).hasSize( 3 );
}
/**
* Get a collection of groups and students from those groups
* that have an avergage mark greater than 5.
*/
public void newCollectorsForGrouping() throws Exception {
// Java 9 new filtering and flatMapping collectors
Map<Group, List<Student>> map = getGroupStream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.flatMapping(
g -> g.getStudents().stream(),
Collectors.filtering(
student -> student.getAverageMark() > 5,
Collectors.toList()
)
)
)
);
// Using Java 8 collectors
map = getGroupStream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.flatMap( g -> g.getStudents().stream() )
.filter( student -> student.getAverageMark() > 5 )
.collect( Collectors.toList() )
)
)
);
}
These collectors can be applied to simple collect operations as well:
public void newStreamsCollectorsSimpleCase() throws Exception {
// Java 8:
List<Student> students = getGroupStream()
.flatMap( gr -> gr.getStudents().stream() )
.filter( student -> student.getAverageMark() > 10L )
.collect( Collectors.toList() );
assertThat( students ).hasSize( 3 );
// new Java 9 Collectors#filtering collector:
students = getGroupStream()
.flatMap( gr -> gr.getStudents().stream() )
.collect( Collectors.filtering( student -> student.getAverageMark() > 10L, Collectors.toList() ) );
assertThat( students ).hasSize( 3 );
// Java 9 collecting right away without other operations:
students = getGroupStream()
.collect(
Collectors.flatMapping(
gr -> gr.getStudents().stream(),
Collectors.filtering(
student -> student.getAverageMark() > 10L,
Collectors.toList()
)
)
);
assertThat( students ).hasSize( 3 );
}

New methods in Stream

All stream interfaces (Stream/IntStream/LongStream/DoubleStream) received next new methods:
  • takeWhile - allows to provide a predicate which will determine when to stop processing stream elements.
  • dropWhile - allows to provide a predicate which will determine till when stream elements will be skipped and not processed.
  • iterate - it's an additional overload of previously present methods, which receives one more parameter. this new parameter is a predicate which determines when to terminate a stream. It allows to create finite streams opposed to previous methods which could crate only infinite streams. The intention of this method is to provide a functional-stream alternative of a for loop with the index.
Besides that Stream interface also received Stream#ofNullable() method which receives on element and if it is a null then an empty stream will be returned (Stream#empty()) or a stream of that one element otherwise. This method might be very useful in cases where some Stream concatenation occurs and some elements might be null, but nulls shouldn't be present. 
Here are a few examples using these new methods:

Conclusions 

All of these additional collectors and stream methods might become quite handy, just one thing that I'd like to add - don't rush and change all your for loops with the iterate+foreach methods use them if it feels appropriate or is needed.

To be continued...