How Predicate maintain state in Java 8

It seems pretty confusing but it’s quite simple. What actually happens is that the distinctByKey method is called only once so there will only ever be one instance of the ConcurrentHashMap and it’s captured by the lambda expression. So when the distinctByKey method returns a Predicate object we then apply that to every element of the stream.


Since this is a capturing lambda, indeed a new Predicate instance will be returned all the time on each call to distinctByKey; but this will happen per entire stream, not per each individual element.

If you are willing to run your example with:

Djdk.internal.lambda.dumpProxyClasses=/Your/Path/Here

you would see that a class is generated for your the implementation of the Predicate. Because this is a stateful lambda - it captures the CHM and Function, it will have a private constructor and a static factory method that returns an instance.


Each call of distinctByKey will produce a different instance, but that instance will be reused for each element of the Stream. Things might be a bit more obvious if you run this example:

 public static <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
    Map<Object,Boolean> seen = new ConcurrentHashMap<>();
    Predicate<T> predicate =  t -> {
        Object obj = keyExtractor.apply(t);
        System.out.println("stream element : " + obj);
        return seen.putIfAbsent(obj, Boolean.TRUE) == null;
    };

    System.out.println("Predicate with hash :" + predicate.hashCode());
    return predicate;
}

@Getter
@AllArgsConstructor
static class User {
    private final String name;
}

public static void main(String[] args) {
    Stream.of(new User("a"), new User("b"))
          .filter(distinctByKey(User::getName))
          .collect(Collectors.toList());

}

This will output:

Predicate with hash :1259475182
stream element : a
stream element : b

A single Predicate for both elements of the Stream.

If you add another filter:

Stream.of(new User("a"), new User("b"))
      .filter(distinctByKey(User::getName))
      .filter(distinctByKey(User::getName))
      .collect(Collectors.toList());

There will be two Predicates:

Predicate with hash :1259475182
Predicate with hash :1072591677
stream element : a
stream element : a
stream element : b
stream element : b