1

This looks like very basic but I fail to understand why my code doesn't compile.

I have a class Person with a String name attribute and a getName() getter.

I create an Employee class extends Person

I have this method :

public Predicate<? extends Person> startsA() {
        return p -> p.getName().startsWith("A");
}

So I want this method to work with all subclasses of Person

But this code doesn't compile :

startsA().test(new Employee());
startsA().test(new Person());

I don't understand why

AntonBoarf
  • 1,239
  • 15
  • 31
  • Just change `Predicate extends Person>` to `Predicate`. – gthanop May 12 '23 at 15:52
  • 2
    Or if you want to allow the method to return `Predicate`, use `public

    Predicate

    `

    – Rob Spoor May 12 '23 at 15:55
  • 1
    Does this answer your question? [Give examples of functions which demonstrate covariance and contravariance in the cases of both overloading and overriding in Java?](https://stackoverflow.com/questions/2501023/give-examples-of-functions-which-demonstrate-covariance-and-contravariance-in-th) – m0skit0 May 12 '23 at 15:57
  • ok thanks. Isn't extends Person> and

    the same thing ?

    – AntonBoarf May 12 '23 at 15:58
  • 2
    No, they are not the same thing. Every Predicate has a specific, non-wildcard type. `Predicate extends Person>` means “this Predicate’s specific type is either Person or some class that inherits Person, but I don’t know what that specific type is.” When you try to call test(new Person()), it is not type-safe, because the *specific type* of the Predicate might be Employee, or Customer, or OlympicAthlete. `? extends Person` means the compiler doesn’t know which specific type the Predicate requires. – VGR May 12 '23 at 16:14

2 Answers2

3

The reason your code doesn't compile is because you have defined the startsA() method to return a Predicate of a wildcard type that extends Person. The wildcard type represents an unknown subtype of Person,

Change startsA() method to return a Predicate<Person> instead of using a wildcard type:

public Predicate<Person> startsA() {
    return p -> p.getName().startsWith("A");
}
Asgar
  • 1,920
  • 2
  • 8
  • 17
  • 1
    Just to add to this answer: any `Employee` is a `Person`, but any `Employee` cannot be a subtype of an unknown subclass of `Person` (which `? extends Person` implies). Same holds for any subclass of `Person` (ie `Employee` is just an example here). – gthanop May 12 '23 at 15:59
1

Generics are one meta level removed. You need to walk a mile in the shoes of the compiler to understand them.

The mistake I often see with generics, and the one you seem to make, is that you think Predicate<? extends Person> means 'this is a predicate that can test any person. An Employee, a Customer - doesn't matter, hence, 'whatever extends Person' right.

But that is not what that means.

After all, imagine it meant that. What is Predicate<Person> then? A predicate that can test only specifically new Person() but not new Employer()? Java doesn't work that way - any instance of Employer can be used as a Person so that would be utterly bizarre.

In fact, Predicate<Person> means what you want here: A predicate object that can test any person.

So what does Predicate<? extends Person> mean? This:

A predicate that can test... I don't actually know what it can test. All I know is, whatever it is, it's some type that is either Person or some subtype thereof.

So what point is there to such a thing? Not all that much in this specific case (for predicate). Let's instead think of lists. We have 3 different types that are relevant (remember, Integer and Double both extend Number, and Number extends Object)

  • List<? extends Number>
  • List<? super Number>
  • List<Number>

You can pass a List<Integer> object to a method that takes a List<? extends Number>. You cannot pass it to a method that takes a List<Number>. Why not - Integer extends Number, right? Ah, but inside generics, that's invariant, as in, no, you can't just replace a type with its subtype. After all, I can add a Double object to a List<Number>. I can add Number objects to my list, a Double object is a Number object, QED.

But, adding a Double object to a List<Integer>, that's no good. Hence, a List<? extends Number> is required - try it, you cannot add things to such a list. For the same reason: List<? extends Number> does not mean: "A list that can hold numbers - any numbers". Quite the opposite. It means: "A list that can hold.. I do not know what it can hold (hence the question mark!) - all I know is, whatever it holds, it's Number. Or any subclass of Number). Given that, list.get(5) WILL return an expression of type Number (given the few things we know, that is safe), but you can't add anything to it (except for the academic case of a literal null, which is all types).

For Predicate in particular, Predicate<? extends> is completely useless, that shouldn't ever show up in any case. Predicate<? super X> can be quite convenient. After all, that means: "A predicate that can test.. well I do not know what it can test. What I do know, is that it is either X, or some super type of X". Given that information, .test(instanceOfX) is fine.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Thank you very much. You perfectly understood what I failed to understand when it comes to generics – AntonBoarf May 14 '23 at 01:04
  • It's useful to mention the PECS (Producer `extends`, Consumer `super`) rule. Since `Predicate` is solely a consumer of its type parameter, it can always be used with `super` wildcards. – newacct May 25 '23 at 05:04