4

I'm trying to create a finder which takes several predicates and reduces them:

public static <T extends BusinessInterface> Collection<T> findOr(
    Context pContext, Class<T> pClass, Predicate<? super T>... pPredicates) {
  Predicate<? super T> lReducedPredicate =
      Arrays.asList(pPredicates).stream().reduce(Predicate::or).orElse(r -> false);
  return find(pContext, pClass, lReducedPredicate);
}

Unfortunately I get following compiler error:

Predicate lReducedPredicate = Arrays.asList(pPredicates).stream().reduce(Predicate::or).orElse(r -> false); incompatible types: Predicate cannot be converted to Predicate where T is a type-variable: T extends BusinessInterface declared in method findOr(Context,Class,Predicate...) where CAP#1,CAP#2 are fresh type-variables: CAP#1 extends Object super: T from capture of ? super T CAP#2 extends Object super: T from capture of ? super T

I get not errors in Eclipse and I have no idea what is going wrong.

Any help is really appreciated :).

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
user1567896
  • 2,398
  • 2
  • 26
  • 43

2 Answers2

7

One way to solve this is using

public static <T extends BusinessInterface> Collection<T> findOr(
    Context pContext, Class<T> pClass, Predicate<? super T>... pPredicates) {
  Predicate<? super T> lReducedPredicate = Arrays.asList(pPredicates).stream()
    .reduce((a,b) -> t -> a.test(t) || b.test(t)).orElse(r -> false);
  return find(pContext, pClass, lReducedPredicate);
}

While you can’t invoke the or method on a Predicate<? super T> instance with another Predicate<? super T> as argument, you can create the same function, or would return, yourself, as passing a T instance to either test method is valid.

Holger
  • 285,553
  • 42
  • 434
  • 765
6

Your code works for me in intellij, but not in ideone, which is strange.

The reason for the compiler error is that you're effectively trying to do this:

Predicate<? super T> a = null;
Predicate<? super T> b = null;
a.or(b);  // Compiler error!

Because of the separate wildcards on both a and b, the compiler can't guarantee that the type bounds are compatible, because those wildcards are not necessarily the same.

Anyway, try this one weird trick:

Arrays.asList(pPredicates)
    .stream()
    .map(a -> a)  // Here!
    .reduce(Predicate::or)
    .orElse(r -> false);

Inserting this line allows it to cast to the type which matches the type constraints.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 2
    @Holger I don't have a good explanation. My intuition is that it gives the type inference logic just a bit more flexibility to choose a type which is compatible with the `reduce(Predicate::or)`. I'd be delighted if you could explain it without my handwaving ;) – Andy Turner Jul 05 '17 at 10:32
  • I'd be interested in the answer as well :). With `.map(a ->a)` I don't get any compiler errors anymore and the unit-tests are working as expected. So, is there any reason to choose Holgers solution prior to Andys? – user1567896 Jul 05 '17 at 10:40
  • 2
    @user1567896: the main reason to prefer my answer would be that we know why it works. As long as we don’t know why the `map(a -> a)` trick works, we have to consider that this could be just a compiler bug that gets fixed in a newer version. A cleaner alternative still using `Predicate::or` would be `Predicate lReducedPredicate = Arrays.asList(pPredicates).stream() .reduce(r -> false, Predicate::or, Predicate::or);` – Holger Jul 05 '17 at 12:20
  • 4
    @Holger blimey, if even *you* can't explain it, it is worth staying away from it. I really was hoping to be enlightened. – Andy Turner Jul 05 '17 at 12:24
  • 2
    @Holger I've posted [a separate question](https://stackoverflow.com/questions/44935248/why-does-adding-mapa-a-allow-this-to-compile) asking about this specifically. – Andy Turner Jul 05 '17 at 20:17