1

I would love to understand why the code below will not compile, and what the correct implementation for the desired functionality should be.

First the simple case, which is not my desired functionality but still strange to me.

My compile errors are:

GenericsTest.java:38: error: method usePredicate2 in class GenericsTest<X> cannot be applied to given types;
    genericsTestChild.usePredicate2(testCollection); // Does not compile
                     ^
  required: Predicate<Collection<?>>
  found:    Predicate<Collection<Parent>>
  reason: argument mismatch; Predicate<Collection<Parent>> cannot be converted to Predicate<Collection<?>>
  where X is a type-variable:
    X extends Parent declared in class GenericsTest

For some reason the wildcard does not match. A Collection<Parent> is not a match to Collection<?> when it is inside a Predicate.

Then for my actual desired functionality:

GenericsTest.java:33: error: method usePredicate1 in class GenericsTest<X> cannot be applied to given types;
    genericsTestParent.usePredicate1(testCollection);  // Does not compile
                      ^
  required: Predicate<? super List<? super Parent>>
  found:    Predicate<Collection<Parent>>
  reason: argument mismatch; Predicate<Collection<Parent>> cannot be converted to Predicate<? super List<? super Parent>>
  where X is a type-variable:
    X extends Parent declared in class GenericsTest

Collection should be within bounds for ? super List and Parent is within bounds for ? super Parent, so the combination should be within bounds, but apparently it is not.

Compilation was done with "javac" from Java 16, using the "-Xdiags:verbose" flags for additional information.

And the code to replicate this:

import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

public class GenericsTest<X extends GenericsTest.Parent> {

  /** A child class. */
  public static class Child extends Parent { }

  /** A parent class. */
  public static class Parent { }


  /** This predicate should be able to handle anything that is a sub-class of Collection containing anything that is a sub-class of Parent. */
  public static class TestCollection implements Predicate<Collection<Parent>> {
    @Override
    public boolean test(Collection<Parent> parent) {
      return false;
    }
  }


  /** Desired functionality : predicate will consume something less specific than List<> of less specific than X. */
  public void usePredicate1(Predicate<? super List<? super X>> predicate) {  }

  /** Another fail-case: Predicate will consume something that is exactly a Collection<> of anything. */
  public void usePredicate2(Predicate<Collection<?>> predicate) {  }

  public static void tryIt() {
    Predicate<Collection<Parent>> testCollection = new TestCollection();

    GenericsTest<Parent> genericsTestParent = new GenericsTest<>();
    genericsTestParent.usePredicate1(testCollection);  // Does not compile
    genericsTestParent.usePredicate2(testCollection);  // Does not compile

    GenericsTest<Child> genericsTestChild = new GenericsTest<>();
    genericsTestChild.usePredicate1(testCollection); // Does not compile
    genericsTestChild.usePredicate2(testCollection); // Does not compile
  }
}
Simon G.
  • 6,587
  • 25
  • 30
  • Regarding `? super List super X>` and the comment just above the method: please do yourself a favor and read through [Java generics super keyword](https://stackoverflow.com/questions/3847162/java-generics-super-keyword). The first answer tells how your expectation is not correct. – ernest_k Jul 15 '21 at 11:45
  • @ernest_k Actually I did read that exact answer before posting. Following the PECS principle, the Predicate is a consumer so uses super. Compare my method with the `Collection.removeIf(Predicate super E>)` method. If you have an answer, please post it. – Simon G. Jul 15 '21 at 12:56
  • 1
    Wildcard matching is not recursive; it only goes one level deep. Try `public void usePredicate2(Predicate> predicate)`. – VGR Jul 15 '21 at 13:21
  • @VGR after some testing, I can see that `public static void test(Collection extends Comparator extends Number>> x)` compiles, and only one of these statements compiles: ` test(List.of(Comparator.naturalOrder())); test(List.of(String.CASE_INSENSITIVE_ORDER));`. This suggests it is more complicated than just being limited to one level deep. – Simon G. Jul 15 '21 at 14:05
  • @VGR Also - why does my `usePredicate2` method not match, when it only uses one level of wildcards? – Simon G. Jul 15 '21 at 14:17
  • Wildcard matching applies one level deep with regard to generics, not with regard to how many wildcards are present. – VGR Jul 15 '21 at 14:53

0 Answers0