21

I'm struggling to get my head around this and was wondering if someone could explain the reasons for this.

I have three classes:

class Angel {}
class Person extends Angel {}
class Employee extends Person {}

When I attempt to execute this code

public static void insertElements(List<? super Person> list){
    list.add(new Person());
    list.add(new Employee());
    list.add(new Angel());
}

I get an error:

The method add(capture#5-of ? super Person) in the type List<capture#5-of ? super Person> is not applicable for the arguments (Angel)

I had always read the documentation to mean <? super X > meant any type of X or a superclass of X (so in my case, Angel is a superclass of Person) and should be permitted? Obviously not!

Does anyone have any simple examples?

Hearen
  • 7,420
  • 4
  • 53
  • 63
Mike
  • 2,391
  • 6
  • 33
  • 72

5 Answers5

31

Your intuitive logic says "a List<? super Person> is a list of things that are a Person or a supertype of Person, so naturally I can add an Angel into it". That interpretation is wrong.

The declaration List<? super Person> list guarantees that list will be of such a type that allows anything that is a Person to be added to the list. Since Angel is not a Person, this is naturally not allowed by the compiler. Consider calling your method with insertElements(new ArrayList<Person>). Would it be okay to add an Angel into such a list? Definitely not.

The best way to reason about it is that List<? super Person> is no definite type: it is a pattern describing a range of types that are allowed as an argument. Look at List<Person> as not a subtype of List<? super Person>, but a type that matches this pattern. The operations allowed on List<? super Person> are those that are allowed on any matching type.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • 2
    Thanks Marko, your answer makes perfect sense. Could I follow up with one question then...what is the difference between ? super and ? extends. I always read ? extends to be ezxactly as you have described ? super. (ie: anything that is-a Person) – Mike Nov 29 '12 at 14:49
  • 1
    `List extends Person>` is a list of Persons or something narrower. In that case you are guarenteed to **get** something that is a Person from it, but you won't be able to add anything of your own into it, because you don't know how narrow the list is. – Marko Topolnik Nov 29 '12 at 14:56
  • In order to do its type erasure work, the compiler has to concretize a wildcard into a specific boundary type. It uses inference. Step 1: compiler infers it's a `List`. Step 2: the compiler verifies that `List`'s `add` method is getting a `Person` or a subtype of `Person`. The clue here is that the compiler says ``CAP#1 extends Object super: Person from capture of ? super Person``. It helps me to think of 'Object super: Person' as _an object of which Person is a supertype_, or more simply, **an object of type Person**. An Angel is not a person, so it fails compilation. – John Jun 23 '20 at 03:27
  • You state "The declaration List super Person> list guarantees that list will be of such a type that allows anything that is a Person", but is not that the description of List extends Person>. I find that very confusing and not a clear distinction at all. – Andrew S Mar 29 '21 at 02:23
  • @AndrewS `List` conforms to `List extends Person>`. But I can't add a `Girl` to that list. Clearly, `List extends Person>` does not guarantee I it will be something I can add anything that is a `Person` to. – Marko Topolnik Mar 29 '21 at 07:36
  • @MarkoTopolnik Thank you for responding so promptly. Could you address List super Person> in your response to my question as that is what I was asking about. – Andrew S Mar 30 '21 at 16:09
  • @AndrewS `List` conforms to `List super Person>` because `Person extends Angel` and indeed, I can add any `Person` to it. `List` does not conform to `List super Person>` and I can't add any person to it. – Marko Topolnik Mar 30 '21 at 19:51
  • Lower bound is very poorly defined on the official oracle pages, even literature for OCP examination struggles to convey thorougly but here I finally get it – downvoteit Oct 28 '21 at 21:07
18

For me, none of these answers were clear enough, although they helped me. After looking a lot, and I mean a lot, I finally came up with the easiest explanation for wildcards:

public class CatTest {

    public class Animal {}
    public class Cat extends Animal {}
    public class MyCat extends Cat {}
    public class Dog extends Animal {}

    public static void main(String[] args) {
        List<Animal> animalList = new ArrayList<>();
        List<Cat> catList = new ArrayList<>();
        List<MyCat> myCatList = new ArrayList<>();
        List<Dog> dogList = new ArrayList<>();

        CatTest catTest = new CatTest();
        // Here you are trying to add a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat, Cat is an Animal, therefore MyCat is an Animal.
        // So you can add a new MyCat() to a list of List<Animal> because it's a list of Animals and MyCat IS an Animal.
        catTest.addMethod(animalList);

        // Here you are trying to add a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat. 
        // So you can add a new MyCat() to a list of List<Cat> because it is a list of Cats, and MyCat IS a Cat
        catTest.addMethod(catList);

        // Here you are trying to add a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat.
        // Everything should work but the problem here is that you restricted (bounded) the type of the lists to be passed to the method to be of
        // a type that is either "Cat" or a supertype of "Cat". While "MyCat" IS a "Cat". It IS NOT a supertype of "Cat". Therefore you cannot use the method
        catTest.addMethod(myCatList); // Doesn't compile

        // Here you are adding a MyCat instance (in the addMethod we create a MyCat instance). MyCat is a Cat. 
        // You cannot call the method here, because "Dog" is not a "Cat" or a supertype of "Cat"
        catTest.addMethod(dogList); // Doesn't compile
    }

    public void addMethod(List<? super Cat> catList) {
        // Adding MyCat works since MyCat is a subtype of whatever type of elements catList contains
        // (for example Cat, Animal, or Object)
        catList.add(new MyCat());
        System.out.println("Cat added");
    }
}

At the end, these are the conclusions:

When working with wildcards, wildcards apply to the type of the list passed as an argument to the method, not to the type of the element when you try to add an element to the list. In the example, you get the following compile error:

The method addMethod(List) in the type CatTest is not applicable for the arguments (List)

As you can see, the error is about the method signature, and not about the body of the method. So you can only pass lists of elements that are either "Cat" or super type of "Cat" (List<Animal>, List<Cat>).

Once you pass a list with the specific type of elements, you can only add elements to the list that are either "Cat" or a subtype of "Cat", that is to say, it behaves as always when you have a collection of elements! You cannot add an "Animal" to a list of "Cat". As I said before, wildcards don't apply to the elements themselves, the restrictions apply only to the "List". Now, why is that? Because of simple, obvious, well known reasons:

Animal animal = new Dog();

If you could add an "Animal" to a "Cat" list, you could also add a "Dog" (a "Dog" is an "Animal"), but it is not a "Cat".

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
FraK
  • 919
  • 1
  • 13
  • 21
2

There is a severe understanding as I always did, List<? super Person> is introduced to ensure List<Person> and List<Angel> can both be passed in as an argument which is the flexibility upper bounded wildcard provides.

But within the method, in your case only Person or subtype of Person can be inserted into the list which ensures the list will always contain valid instances.

E.g. if you are passing in a list of Person, then only Employee and Person can be inserted while Employee will be auto-type-casted to Person.

Hearen
  • 7,420
  • 4
  • 53
  • 63
  • 1. `List super Person>` is not *upper bounded wildcard*; it is *lower bounded wildcard*. 2. You can't insert an `Angel`(subtype of `Person`) instance into the list within the method. You can only insert `Person`, `Employee` instance or `null` value. – rosshjb Dec 12 '21 at 10:52
0

Think about it the other way around: For the type bound on List<? super Person>, List<Person> is obviously a valid type for the parameter. If the compiler allowed your code, it would allow you to insert something of type Angel into a List<Person>.

Mathias Schwarz
  • 7,099
  • 23
  • 28
0

Wildcards basically allow you to specify a type range for a parameter, in your case when you write

public static void insertElements(List<? super Person> list), the compiler will allow you to pass a List of either Person or any of its super types. Lets see a concrete example

List<Object> objects = new ArrayList<>();
List<Angel> angels = new ArrayList<>();
List<Person> persons = new ArrayList<>();
List<Employee> employees = new ArrayList<>();

insertElements(objects);      // Works fine, Object is super type of every type
insertElements(angels);       // Works fine, Angel is super type of Person
insertElements(persons);      // Works fine, Person itself
insertElements(employees);    // Error, Employee is neither Person itself nor its a super type of Person

Now lets see why are you getting that error

From above code we know that you can pass list of persons, angels or objects to the insertElements and it will work fine, Now lets say you invoke the method with list of persons. so list is now List<Person> and you do list.add(Angel());

this essentially means you are trying to do Person person = new Angel(); this assignment is not type compatible and you get the error.

mightyWOZ
  • 7,946
  • 3
  • 29
  • 46