Struggling with understanding <? extends T> wildcard in Java

后端 未结 2 341
梦毁少年i
梦毁少年i 2020-12-06 21:59

I have a very basic question.

The code below doesn\'t compile (assume Apple Extends Fruit):

    List numbers = new ArrayList&l         


        
相关标签:
2条回答
  • 2020-12-06 22:39

    The best way to understand this is to think of the wildcard as saying something about the list, not the fruit. In other words:

    List<Banana> allBananas = getMyBananas();
    enumerateMyFruit(allBananas);
    
    static void enumerateMyFruit(List<? extends Fruit> myFruit) {
        for (Fruit fruit : myFruit)
            System.out.println(fruit);
    }
    

    When we pass allBananas to enumerateMyFruit, inside the method we lose information about the original declared type of the list. In this example we can very clearly see why we shouldn't be able to e.g. put apples in a List<? extends Fruit>, because we know that the list is actually a List<Banana>. Again, the wildcard is telling us something about the declared type of the list.

    List<? extends Fruit> should be read as something like "a list originally declared to hold Fruit or some subtype of Fruit, but we don't know what that declared type is anymore". All that we know is that everything we pull out of the list is a Fruit.

    Also, you are right, we could iterate the list and use instanceof to find out what is really in the list, but this wouldn't tell us the original declared type of the list. In the above code snippet we would find out that everything in the list turned out to be a Banana, but I could have just as easily declared allBananas as a List<Fruit>.


    You might also see why a List<Dog> is not a List<Animal> which explains some of this. The wildcard is how we have covariance among generic types. a List<Dog> is not a List<Animal> but it is a List<? extends Animal>. This comes with the restriction that we can't add to a List<? extends Animal>, because it might be a List<Dog>, a List<Cat> or something else. We don't know anymore.

    There's also the ? super, which is the opposite. We can store Fruit in a List<? super Fruit> but we don't know what kinds of objects we will pull out of it. Its original declared type might actually be e.g. a List<Object>, with all kinds of other stuff in it.

    0 讨论(0)
  • 2020-12-06 22:40

    First remember that for generic parameters without wildcards, you can't substitute one for another. If a method takes a List<Fruit> it won't take a List<Apple>, it has to be an exact match. Also remember this is about the static type of the variable, there's no direct connection to the contents. Even if your List<Fruit> contains all Apples, you still can't substitute it for a List<Apple>. So we're talking about type declarations, not about what's in the collections.

    Also remember instanceof is done at runtime, generics work at compile time. Generics are about helping the compiler figure out what types things are so you don't have to resort to instanceof and casting.

    When a method foo takes a parameter with the generic type List<? extends Fruit>, that is a way of saying that the method can take a range of types, in this situation those being any of the following:

    • You can pass in a List<Fruit>

    • You can pass in a List<Banana>

    • You can pass in a List<Apple>

    (etc., for whatever subtypes of Fruit you have)

    So your method can work with a list of any of these, however, the method body has to be valid for any of them. When you add an Apple to the list, that works for the case where what's passed in is a List<Apple>, it works for List<Fruit>, for List<Banana> not so much. (And making Fruit concrete doesn't help matters, adding a Fruit doesn't work for the List<Apple> case either.)

    That is why there's a rule that anytime the wildcard type extends something, adding stuff is not possible, there's no way it can work for all the possible types that can be passed in.

    0 讨论(0)
提交回复
热议问题