Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why many Java Stream interface methods use lower bounded wildcard in parameters instead of generic type?

Many Java Stream interface methods use lower bounded wildcard in parameters

for example

Stream<T> filter(Predicate<? super T> pred)

and

void forEach(Consumer<? super T> action)

What is the advantage of using Predicate<? super T> over Predicate<T> here?

I understand, with Predicate<? super T> as parameter, Predicate object of T and super types can be passed into the method but i can't think of a situation where a Predicate of super type needed over the specific type?

For example if i have a Stream<Integer> i can pass Predicate<Integer>, Predicate<Number>, and Predicate<Object> objects as arguments to its filter method but why would anyone pass a Predicate<Object> over Predicate<Integer>?

What is the advantage of using <? super T> here?

like image 941
Arjun Avatar asked May 13 '18 07:05

Arjun


1 Answers

I assume you are aware of the PECS pattern, which is useful to adhere to when designing an API, even if no actual use case jumps into your eye. When we look at the final state of Java 8 and typical use cases, it’s tempting to think that this was not needed anymore. Not only do lambda expressions and method references infer the target type even when actually using a more general type, the improved type inference applies to method invocations as well. E.g.

Stream.of("a", "b", "c").filter(Predicate.isEqual("b"));

would require the filter(Predicate<? super T>) declaration with a pre-Java 8 compiler, as it would infer Predicate<Object> for the expression Predicate.isEqual("b"). But with Java 8, it would also work with Predicate<T> as parameter type, as the target type is used for nested method invocations as well.

We may consider that the development of the Stream API and the new Java language version/ compiler implementation happened at the same time, so there might have been a practical reason to use the PECS pattern at the beginning, while there never was a reason not to use that pattern. It still raises the flexibility when it comes to reusing existing predicate, function or consumer instances and even if that might not be much common, it doesn’t hurt.

Note that while, e.g. Stream.of(10, 12.5).filter(n -> n.doubleValue() >= 10), works, because the predicate may get an inferred non-denotable type suitable to process the Stream’s element type “#1 extends Number & Comparable<#1>”, you can not declare a variable of that type. If you want to store the predicate in a variable, you have to use, e.g.

Predicate<Number> name = n -> n.doubleValue()>=10;
Stream.of(10, 12.5).filter(name);

which only works, if filter has been declared as filter(Predicate<? super T> predicate). Or you enforce a different element type for the Stream,

Predicate<Number> name = n -> n.doubleValue()>=10;
Stream.<Number>of(10, 12.5).filter(name);

which already demonstrates how omitting the ? super at the filter declaration may cause more verbosity on the caller’s side. Also, enforcing a more general element type may not be an option if the more specific type is needed in a later pipeline stage.

While existing function implementations are rare, there are some, e.g.

Stream.Builder<Number> b = Stream.builder();
IntStream.range(0, 10).boxed().forEach(b);
LongStream.range(0, 10).boxed().forEach(b);
Stream<Number> s = b.build();

wouldn’t work without the ? super in the forEach(Consumer<? super T> action) declaration.

A case you may encounter more often, is to have an existing Comparator implementation which you might want to pass to a sorted method of a Stream with a more specific element type, e.g,

Stream.of("FOO", "bar", "Baz")
      .sorted(Collator.getInstance())
      .forEachOrdered(System.out::println);

wouldn’t work without the ? super in the sorted(Comparator<? super T> comparator) declaration.

like image 183
Holger Avatar answered Sep 24 '22 13:09

Holger