6

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?

Arjun
  • 1,116
  • 3
  • 24
  • 44
  • 1
    "why would anyone pass a `Predicate` over `Predicate`" - because they *have* a `Predicate`? – user2357112 May 13 '18 at 07:40
  • 1
    a) from a type system point of view ... this is possible B) from a convenience point of view ... it is helpful. So why *not* do it that way?! – GhostCat May 13 '18 at 07:45
  • 2
    I believe this is not a duplicate. OP clearly understands what `super` in this context means. OP does not understand _why_ `Stream` methods are all co/contravariant. To be honest, I kind of want a practical example where this is used. – Sweeper May 13 '18 at 07:52
  • This [answer](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) will help you to understand this – Alexander.Furer May 13 '18 at 08:30

1 Answers1

7

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    such a good answer - a pity it got little attention though. For me it means one thing really - still use PECS – Eugene May 15 '18 at 12:14
  • @Eugene indeed, the bottom line is, PECS is still the way to go. – Holger May 15 '18 at 16:59
  • one more note: if `filter` was declared without the lower bound (ie: as `filter(Predicate p)` ) then alternatively to enforcing more general type for the stream, one could also do `myIntStream.filter(myNumberPredicate::test)` which solves the problem of more specific type of the stream being required later in the pipeline (for the price of a tiny tiny overhead, that will probably get optimized out by any compiler/VM). – morgwai Aug 28 '22 at 20:26