简体   繁体   中英

Java lower bound wildcards

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 > <? 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?

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 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> 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> List<? super Person> , but a type that matches this pattern. The operations allowed on List<? super Person> List<? super Person> are those that are allowed on any matching type.

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

There is a severe understanding as I always did, List<? super Person> 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.

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

Think about it the other way around: For the type bound on List<? super Person> 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> .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM