Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java lower bound wildcards

Tags:

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?

like image 648
Mike Avatar asked Nov 29 '12 10:11

Mike


2 Answers

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.

like image 123
Marko Topolnik Avatar answered Nov 19 '22 21:11

Marko Topolnik


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".

like image 41
FraK Avatar answered Nov 19 '22 19:11

FraK