chiperortiz
chiperortiz

Reputation: 4991

Java 8 predicates using methods bodies are called only once?

I have examined the following snippet:

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Map<Object, Boolean> computed = new ConcurrentHashMap<>();/*IS THIS LINE CALLED ONCE ON STREAM->FILTER NOT MATTER HOW LONG THE STREAM IS*/
    return t -> {return computed.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;};
}

private void test(){
    final long d = Stream.of("JOHN","STEPHEN","ORTIZ","RONDON")
            .filter(distinctByKey(Function.identity()))
            .count();
    System.out.println("d = " + d);
}

This code is not mine. I know that using a ConcurrentMap is not the right choice in this example and I should use ConcurrentMap instead of Map in this case, but this is not my concern now.

I thought that the distinctByKey method is called or interpreted in each iteration of the Stream. I mean the Map being instantiated in each turn, but it's not!

Is the body of the Predicate method called only once?

In the Stream iteration, is this an assertion?

Because when I try the following code:

final Function<String,Integer>a = (name)->name.length();
System.out.println(distinctByKey(a).test("JOHN"));
System.out.println(distinctByKey(a).test("STEPHEN"));
System.out.println(distinctByKey(a).test("ORTIZ"));
System.out.println(distinctByKey(a).test("RONDON"));

I can see that the body of the method is indeed called in each line. What makes the body of the filter to only be called once?

Upvotes: 3

Views: 1133

Answers (3)

John Bollinger
John Bollinger

Reputation: 181149

I thought that the distinctByKey method is called or interpreted in each iteration of the Stream i mean the Map being instance in each turn but it's not! my question is the body of the Predicate method call only one time? in the Stream iteration is this a assertion?

No. Streams are not magic, and they do not overthrow standard Java semantics. Consider the code presented:

    final long d = Stream.of("JOHN","STEPHEN","ORTIZ","RONDON")
            .filter(distinctByKey(Function.identity()))
            .count();

Taking specific types and methods out of the picture, that has this general form:

long x = A.b(y).c(z).d(w);

There's no reason to expect that any of a(), b(), or c() is invoked more than once in that chain, and or that their arguments are evaluated more than once each. That is not affected by some of the types being Stream.

What happens instead in your case is that the Predicate returned by (the sole invocation of) your distinctByKey() method is used more than once as the stream in which it is embedded is processed. That Predicate contains a reference to a Map, which it uses and modifies in performing its work.

Upvotes: 4

Eran
Eran

Reputation: 393986

When you call .filter(distinctByKey(Function.identity())), the argument passed to filter() is evaluated. That's the only time distinctByKey(Function.identity()) is executed and returns an instance of Predicate<String>.

That Predicate is then evaluated (i.e. it's test() method is executed) multiple times, each time for a different element of the Stream.

To make your last snippet behave similar to the Stream pipeline, it should look like this:

final Function<String,Integer> a = (name)->name.length();
Predicate<String> pred = distinctByKey(a);
System.out.println(pred.test("JOHN"));
System.out.println(pred.test("STEPHEN"));
System.out.println(pred.test("ORTIZ"));
System.out.println(pred.test("RONDON"));

Upvotes: 4

Eugene
Eugene

Reputation: 120988

distinctByKey returns a single instance of the Predicate that caches the ConcurrentHashMap. You could achieve almost the same thing if you replace the creation of the Predicate via the lambda with an anonymous inner class for example.

Upvotes: 4

Related Questions