Java Generics Wildcard Question: List<? Extends A>

Java Generics WildCard Question: List? extends A

Method arguments are contravariant in the subtype, and by the definition of the wildcard, for every type T that extends Vehicle Foo<T> is a subtype of Foo<* extends Vehicle>. The implication of this is that wildcards are great when you only care about the return type, but dont work in situations like this when you want to pass a value of the type to a method.

The problem is that a user might try to call

List<SpaceShip> l = ...
addCars(l);

if your code were to compile, l would then be a list of spaceships containing 3 cars. Clearly no good.

Java Generics WildCard: ? extends Number vs T extends Number

There is no difference in this case, because T is never used again.

The reason for declaring a T is so that you can refer to it again, thus binding two parameter types, or a return type together.

Java Generic Type for Wildcard extends allow to add only null

If you go through the Generic Guidelines stated by Oracle docs, these kind of list declarations are tend to be read-only (no write operations can be performed)

Quoted from the page

A list defined by List<? extends ...> can be informally thought of as
read-only, but that is not a strict guarantee.

As per your example, let us assume Child2 and Child3 extends Parent, then

List<? extends Parent> list can be a list of any of these subclasses. Adding a specific subclass element can cause runtime error, thus compiler complains in advance

Use of '? extends ' and '? super ' in Collection generics

The wildcards introduce restrictions in how the collection can be used.

For example, with List<? extends Number>, I can't add new elements to the list. This is because all I know is that the list is some kind of subtype of Number, but I don't know what that actual subtype is (so how could I know what to add?). For example, take the following code:

public void doSomethingWith(List<? extends Number> numbers) {
numbers.add(Integer.valueOf(0)); // Won't compile
}

This won't compile because both of these method calls are legal:

doSomethingWith(new ArrayList<Integer>());
doSomethingWith(new ArrayList<Double>());

What you can do is read elements from the list:

// This will all compile
public void doSomethingWith(List<? extends Number> numbers) {
for (Number number : numbers) {
// Do something with number
}
// OR
Number number = numbers.get(0);
// OR
Number number = numbers.remove(0);
}

Calls to methods like get will return some kind of Number, we know that for a fact because of the ? extends Number, so we can treat it like that for reading purposes.

On the other hand, List<? super Integer> has exactly the opposite result. I can no longer read from the list, but I can write to it. I know that whatever ? is, it will definitely be a super-class of Integer, so concrete types of the list will definitely accept Integer values. For example:

public void doSomethingWith(List<? super Integer> integers) {
integers.add(Integer.valueOf(0));
}

That code is completely legal. However, if you want to read from the list, the only way to do this is to use Object since anything else requires casting (which requires knowing its concrete type):

for (Object obj : integers)
// OR
Object obj = integers.get(0);
// OR
Object obj = integers.remove(0);

What's Really Happening

Here's what's actually happening. When you specify ? extends Number, you're making any method that takes elements as a parameter unusable. In fact, if you try to auto-complete code in Eclipse using Ctrl+Space on a List<? extends Number>, it shows null as the parameters' types in the add methods and the like. Meanwhile, all the methods that return elements are guaranteed to return at least some kind of Number, though you won't know exactly which subclass of Number it might actually be.

When you specify ? super Integer, you're making any method that takes elements as a parameter guarantee that they'll accept Integer values (and sub-classes of Integer as well). This allows you to call methods like add since you know they'll accept Integer types. Meanwhile, all methods that return elements are only guaranteed to return something, but we don't know what, so all the methods that return elements are only guaranteed to return Object.

PECS is an excellent acronym to remember this, it means "Producer Extends, Consumer Supers". This means that if you want your list to give you something, it's a producer, and you should use extends. If you want your list to accept things from you, it's a consumer, so you use super. See this answer for more.

But what if I have a wildcard with no bounds?

It does both! <?> restricts you from calling methods that take the generic type as an argument and causes all the methods that return the generic type to return Object. This is because we have no idea what the type is whatsoever. For example, all of these assignments into a List<?> are legal:

List<?> list;
list = new ArrayList<Integer>();
list = new ArrayList<String>();
list = new ArrayList<MyClass>();

And so on.

When to use wildcards in Java Generics?

The big difference between

public <T extends Animal> void takeThing(ArrayList<T> list)

and

public void takeThing(ArrayList<? extends Animal> list)

is that in the former method you can refer to "T" within the method as the concrete class that was given. In the second method you cannot do this.

Here a more complex example to illustrate this:

// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
Map<T, String> names = new HashMap<T, String>();
for (T animal : list) {
names.put(animal, animal.getName()); // I assume there is a getName() method
}
return names;
}

// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
Map<Animal, String> names = new HashMap<Animal, String>();
for (Animal animal : list) {
names.put(animal, animal.getName()); // I assume there is a getName() method
}
return names;
}

With the first method if you pass in an List of Cats you get a Map with Cat as key. The second method would always return a Map with general Animal key.

By the way this is not valid java syntax:

public <? extends Animal> void takeThing(ArrayList<?> list)

Using this form of generic method declaration you have to use a valid java identifier and not "?".

Edit:

The form "? extends Type" only applies to variable or parameter type declaration. Within a generic method declration it has to be "Identifier extends Type" as you are able to refer to the "Identifier" from within your method.

Can't add value to the Java collection with wildcard generic type

It's doing that for the sake of safety. Imagine if it worked:

List<Child> childList = new ArrayList<Child>();
childList.add(new Child());

List<? extends Parent> parentList = childList;
parentList.set(0, new Parent());

Child child = childList.get(0); // No! It's not a child! Type safety is broken...

The meaning of List<? extends Parent> is "The is a list of some type which extends Parent. We don't know which type - it could be a List<Parent>, a List<Child>, or a List<GrandChild>." That makes it safe to fetch any items out of the List<T> API and convert from T to Parent, but it's not safe to call in to the List<T> API converting from Parent to T... because that conversion may be invalid.



Related Topics



Leave a reply



Submit