39

I have a list of employees. They have isActive boolean field. I would like to divide employees into two lists: activeEmployees and formerEmployees. Is it possible to do using Stream API? What is the most sophisticated way?

Rudziankoŭ
  • 10,681
  • 20
  • 92
  • 192
  • 2
    Possible duplicate of [How to partition a list by predicate using java8?](https://stackoverflow.com/questions/36678571/how-to-partition-a-list-by-predicate-using-java8) – Malte Hartwig Oct 26 '17 at 15:47
  • @MalteHartwig note that duplicate *says* "predicate", but OP is actually asking about grouping by a function. I wouldn't reopen if it were closed as that, though. – Andy Turner Oct 26 '17 at 15:54

5 Answers5

51

Collectors.partitioningBy:

Map<Boolean, List<Employee>> partitioned = 
    listOfEmployees.stream().collect(
        Collectors.partitioningBy(Employee::isActive));

The resulting map contains two lists, corresponding to whether or not the predicate was matched:

List<Employee> activeEmployees = partitioned.get(true);
List<Employee> formerEmployees = partitioned.get(false);

There are a couple of reasons to use partitioningBy over groupingBy (as suggested by Juan Carlos Mendoza):

Firstly, the parameter of groupingBy is a Function<Employee, Boolean> (in this case), and so there is a possibility of passing it a function which can return null, meaning there would be a 3rd partition if that function returns null for any of the employees. partitioningBy uses a Predicate<Employee>, so it can only ever return 2 partitions. which would result in a NullPointerException being thrown by the collector: whilst not documented explicitly, an exception is explicitly thrown for null keys, presumably because of the behavior of Map.computeIfAbsent that "If the function returns null no mapping is recorded", meaning elements would otherwise be dropped silently from the output. (Thanks to lczapski for pointing this out).

Secondly, you get two lists (*) in the resulting map with partitioningBy; with groupingBy, you only get key/value pairs where elements map to the given key:

System.out.println(
    Stream.empty().collect(Collectors.partitioningBy(a -> false)));
// Output: {false=[], true=[]}

System.out.println(
    Stream.empty().collect(Collectors.groupingBy(a -> false)));
// Output: {}

(*) This behavior isn't documented in the Java 8 Javadoc, but it was added for Java 9.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • and third, the Map that you get is an optimized one internally, to hold only two keys. – Eugene Sep 19 '18 at 07:00
  • 1
    I was curious about this: "passing it a function which can return null, meaning there would be a 3rd partition if that function returns null". I have created a code that return null `Stream.of(1,2,3,4).collect(groupingBy(x -> x == 3 ? null : x >= 3))` and after execution an exception was returned: `java.lang.NullPointerException: element cannot be mapped to a null key`. So it cannot be true. – lczapski Sep 09 '19 at 07:25
  • @lczapski interesting, I'll update the answer. That's not actually [documented](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#groupingBy-java.util.function.Function-), though. – Andy Turner Sep 09 '19 at 08:52
  • @lczapski I guess this restriction comes implicitly from [`Map.computeIfAbsent`](https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#computeIfAbsent-K-java.util.function.Function-), which says that "If the function returns null no mapping is recorded". – Andy Turner Sep 09 '19 at 08:58
8

You can also use groupingBy in this case as there are 2 group posibilities (active and inactive employees):

Map<Boolean, List<Employee>> grouped = employees.stream()
                .collect(Collectors.groupingBy(Employee::isActive));

List<Employee> activeEmployees = grouped.get(true);
List<Employee> formerEmployees = grouped.get(false);
Juan Carlos Mendoza
  • 5,736
  • 7
  • 25
  • 50
  • 5
    +1, but note that you should be *slightly* careful using this approach: the parameter of `groupingBy` is a `Function`, and so there is a possibility of passing it a function which can return `null`, meaning there would be a 3rd partition if that function returns null for any of the employees. The `partitioningBy` uses a `Predicate`, so it can only ever return 2 partitions. – Andy Turner Oct 26 '17 at 15:34
  • 1
    I just did a little experimentation, and found there are other reasons not to use `groupingBy` - check out the edit to my answer. (Sorry, I'm definitely not just trying to rip your answer, I actually learned something by trying the two!) – Andy Turner Oct 26 '17 at 15:41
  • @AndyTurner thanks. For this case I'm assuming that `isActive` won't return null (like it uses a primitive boolean). – Juan Carlos Mendoza Oct 26 '17 at 15:48
  • 1
    I'd assume that is the case too. I'm just pointing out there is the *possibility* via `groupingBy`. – Andy Turner Oct 26 '17 at 15:53
5

What is the most sophisticated way?

Java 12 of course with new Collectors::teeing

List<List<Employee>> divided = employees.stream().collect(
      Collectors.teeing(
              Collectors.filtering(Employee::isActive, Collectors.toList()),
              Collectors.filtering(Predicate.not(Employee::isActive), Collectors.toList()),
              List::of
      ));

System.out.println(divided.get(0));  //active
System.out.println(divided.get(1));  //inactive
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
Adrian
  • 2,984
  • 15
  • 27
3

If you are open to using a third-party library, this will work using Collectors2.partition from Eclipse Collections.

PartitionMutableList<Employee> partition =
        employees.stream().collect(
                Collectors2.partition(Employee::isActive, PartitionFastList::new));

List<Employee> activeEmployees = partition.getSelected();
List<Employee> formerEmployees = partition.getRejected();

You can also simplify things using ListIterate.

PartitionMutableList<Employee> partition =
        ListIterate.partition(employees, Employee::isActive);

List<Employee> activeEmployees = partition.getSelected();
List<Employee> formerEmployees = partition.getRejected();

PartitionMutableList is a type that extends from PartitionIterable. Every subtype of PartitionIterable has a collection for positive results getSelected() and negative results getRejected().

Note: I am a committer for Eclipse Collections.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
0

An easier, cleaner way is using stream.filter() and collect() like this:

activeEmployees = employees.stream().filter(Employee::isActive).collect(Collectors.toList());
formerEmployees = employees.stream().filter(employee -> !employee.isActive()).collect(Collectors.toList());
Kai-Sheng Yang
  • 1,535
  • 4
  • 15
  • 21