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 Predicate
s:
Predicate with hash :1259475182
Predicate with hash :1072591677
stream element : a
stream element : a
stream element : b
stream element : b