3

I'm having trouble using Java Predicates.

I have an enum:

public enum ValidationErrors {
    INCONSISTENT_IS_CITATION_ERROR((SacmElement element)->{
        return !element.getCitedElement().isPresent() || element.getIsCitation();} );

    private final Predicate<? extends SacmElement> isValid;

    ValidationErrors(Predicate<? extends SacmElement> isValid) {
        this.isValid = isValid;
    }

    public Predicate<? extends SacmElement> getIsValid() {
        return isValid;
    }
}

Now within a member of the class SacmElement I try to do the test:

if (ValidationErrors.INCONSISTENT_IS_CITATION_ERROR.getIsValid().test(this))

That doesn't compile because "The method test(capture#1-of ? extends SacmElement) in the type Predicate is not applicable for the arguments (SacmElement).

I don't understand the error: I thought that for the purposes of Java generics a class is considered to extend itself. What am I doing wrong?

digitig
  • 1,989
  • 3
  • 25
  • 45

1 Answers1

3

A good rule of thumb to remember is PECS: Producer Extends, Consumer Super. A predicate is a consumer, so it should use super. Use Predicate<? super SacmElement> instead of Predicate<? extends SacmElement>.

With super, you could have a predicate that checks SacmElements, or you could have one that's even more generic and checks any old Object. A Predicate<Object> would work here, wouldn't it?

With extends, you may have yourself a predicate that can only handle some specific sub-class of SacmElement rather than all SacmElements. That's no good. What if you're trying to test a regular SacmElement but the predicate only accepts SpecificSacmElements?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • No, I don't want to allow predicates that check any old `Object`; I only want to allow predicates that check `SacmElement` or a descendant of it. I *want* some predicates that only handle specific subclasses of `SacmElement`, and that will cause compile-time errors if I call `test(this)` from a class that isn't the right subclass. If I use `Predicate super SacmElement>` and I have a class `ModelElement extends SacmElement`, I won't be able to specify a `Predicate` for a member of the `enum`, will I? – digitig Jul 02 '18 at 13:43
  • In your example, `this` is a `SacmElement`, so `getIsValid()` needs to return a `Predicate` where `X` is either `SacmElement` or a superclass of it. A more generic predicate can still accept a `SacmElement`, but a more specific one cannot. If you have some other use case where you only want to be able to check `ModelElement`s then that'd be a different variable with a different generic type. You wouldn't be able to use this `isValid` there. – John Kugelman Jul 02 '18 at 18:42
  • Still confused. Oracle's Java tutorial says, "To write the method that works on lists of Number and the subtypes of Number, such as Integer, Double, and Float, you would specify List extends Number>. The term List is more restrictive than List extends Number> because the former matches a list of type Number only, whereas the latter matches a list of type Number or any of its subclasses." I want a predicate that will work for SacmElement and *subclasses* of SacmElement, not superclasses: why does `Predicate extends SacmElement>` not do that? The documentation seems to say it can. – digitig Jul 02 '18 at 21:46
  • When you have a `Predicate`, the `test(SacmElement)` method will accept any `SacmElement` or subclass of `SacmElement` as a parameter. Remember, that's how method calls in general work. It's not anything to do with generics yet. Now if you have a `Predicate` then `test(Object)` can accept `SacmElement`s and any of its subclasses, because all of these are `Object`s. That gives you the variance you want. On the other hand, if you have a `Predicate` then the test method will be `test(ModelElement)` and it will accept `ModelElement`s **but not `SacmElements`**. – John Kugelman Jul 03 '18 at 00:05
  • In other words, use regular old parameter polymorphism to get the behavior you want. You don't need `extends` to get it. – John Kugelman Jul 03 '18 at 00:05