简体   繁体   中英

Can I use generics wildcard in List declaration?

Consider the following codes:

class Super {}
class Sub extends Super {}
class Test {
    public static void main(String[] args) {
        List<? extends Super> list = new ArrayList<Sub>(); //1
        list.add(new Sub()); //2
    }
}

Line 1 compiles successfully, but line 2 fails compilation:

The method add(capture#2-of ? extends Super) in the type List<capture#2-of ? extends Super> is not applicable for the arguments (Sub)

My questions are:
1) Why does line 1 successfully compile?
2) Is line 1 a good practice in declaring a List (or other collections)?
3) Why does line 2 fail compilation, after list was declared to be type Sub in line 1?
4) Eclipse's autocompletion says only "null" elements are allowed in list now. Why?

Thanks a lot!

1) Why does line 1 successfully compile?

The first line compiles because List<Sub> is subclass of List<? extends Super> List<? extends Super> and that can only be true if List does not allow you to add any new members to it.

? means that you don't exactly know that it is a List<Sub> or List<Sub1> , so it is unsafe to allow adding of new elements into the list and so it does not allow it.

2) Is line 1 a good practice in declaring a List (or other collections)?

If you already know that it is going to be List<Sub> then I don't find any use, but wildcards are used much when you are passing the List around to other classes like Utilities.

3) Why does line 2 fail compilation, after list was declared to be type Sub in line 1?

Because as I already explained it is unsafe to add any element to list when you dont know the exact type.

4) Eclipse's autocompletion says only "null" elements are allowed in list now. Why?

Because null is of every reference type, this is why you can assign any object a value of null .

Always remember PECS (Producer Extends Consumer Super) rule by Josh Bloch when working with generics

Good References:

Capturing declarations as in line 1 are good in method arguments. See Collection.addAll(Collection<? extends E>) . If you need a list with something extends Super , just use List<Super> .

You have declared your list as, it can be assigned to any sub type of Super class. So, assigning a Sub type list is OK, Compiler allows to compile. But that doesn't mean, you can add a specific Object type into it.

<? extends Super> <? extends Super> don't means, you can add any sub type of Super. It means, You can assign any sub type collection to it.

1) Why does line 1 successfully compile?

You basically define the list to contain elements of any type that extends (or is) Super , ie the compiler knows that every element in that list should at least have the properties of Super .

Since Sub is a subclass of Super and thus any list containing only Sub elements also meets the requirement of all elements being instances of Super , List<? extends Super> list = new ArrayList<Sub>(); List<? extends Super> list = new ArrayList<Sub>(); is correct.

2) Is line 1 a good practice in declaring a List (or other collections)?

As a local variable that depends on personal style, IMHO. When declaring parameters that way (or instance/static variables) that's often not only good style but also needed.

Consider a method that iterates over a collection of numbers and returns a sum. You could declare the parameter to be Collection<Number> but then you couldn't pass a Collection<Integer> without a nasty cast. If the parameter is declared as Collection<? extends Number> Collection<? extends Number> you can pass Collection<Integer> .

3) Why does line 2 fail compilation, after list was declared to be type Sub in line 1?

The reason is that the compiler doesn't know the exact type of the elements in the list. Is it a list of Super or a list of Sub ?

As an example take List<? extends Number> list List<? extends Number> list . You don't know whether you have a List<Number> , a List<Double> or a List<Integer> and thus can't tell whether list.add( new Integer(1) ); would be ok or not. That's how the compiler sees it.

4) Eclipse's autocompletion says only "null" elements are allowed in list now. Why?

I'd have to guess here but adding null to a list would be ok since no matter what type the actual list declares, you can always cast null to that type.

1) Why does line 1 successfully compile?

You declare list to be "a list of something that derives from Super ". You assign a list of Sub to it. Sub is "something that derives from Super ".

2) Is line 1 a good practice in declaring a List (or other collections)?

No. Wildcards are for function parameters. Local variables should be as specific as possible in their generics arguments to avoid problems such as the one you're facing.

3) Why does line 2 fail compilation, after list was declared to be type Sub in line 1?

Fallacy. list was declared to have elements of "something that derives from Super ", not Sub as you claim. And you can't add a Sub to the list because the "something" might be something other than Sub ; it might be Sub2 , and adding would be equivalent to this illegal assignment:

class Super {}
class Sub extends Super {}
class Sub2 extends Super {}
Sub2 s = new Sub();

The key misunderstanding here seems to be that you think the wildcard somehow gets substituted on assignment. It isn't. It remains a wildcard, and only compatibility checks are done.

4) Eclipse's autocompletion says only "null" elements are allowed in list now. Why?

null is the only value that is of any possible reference type, and is therefore compatible with the list no matter what the wildcard stands for.

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