Wildcards
In the previous tutorial we saw that there is no relationship, what so ever, between List<Pet>
, List<Dog>
and List<Bulldog>
, even though Pet, Dog and Bulldog belongs to same family. Each of these list is distinct and doesn’t belongs to any single group or family; moreover, we can’t assign one to another. The wildcards helps us to overcome this restriction.
In this tutorial we use following class hierarchy:
Unbounded Wildcard
Consider a method to display the elements of a List.
public void display(List<Pet> list) {
// display the list elements
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>()); // error
display(new ArrayList<Bulldog>()); // error
display(new ArrayList<Cat>()); // error
display(new ArrayList<Persian>()); // error
The method can display only the list of Pet and nothing else. As explained in the previous tutorial, the parameterized type with different type arguments are not compatible with each other. This rule is also applicable to parameters of methods i.e the List<Pet>
, parameter of display(), can accept only the list of Pet and nothing else. This makes the display() quite restrictive. We can overcome this restriction using wildcard - ?
.
Replace List<Pet>
with List<?>
public void display(List<?> list) {
// display the list elements
}
display(new ArrayList<Pet>());
display(new ArrayList<Dog>());
display(new ArrayList<Bulldog>());
display(new ArrayList<Cat>());
display(new ArrayList<Persian>());
display(new ArrayList<String>());
The List<?>
means list of unknown type, and now we can pass list of any type; even the List<String>
, List<Integer>
etc.,
It is also applicable to parameterized variables.
List<Bulldog> bulldog = new ArrayList<>();
List<Dog> dogs = bulldog; // error
List<?> any = bulldog;
Even though Bulldog is subtype of Dog, the List<Bulldog>
and List<Dog>
are not compatible, and compiler doesn’t allow to assign one to another. However, the variable List<?>
accepts list of any type. In essence, the wildcard ?
groups the lists of any type as a single family.
Wildcard Restrictions
Can we reassign List<?> any
to variable of specific type? Answer is no.
List<String> strings = new ArrayList<>();
List<?> any = strings;
List<String> stringsCopy = any; // error
strings = any; // error, even though strings and any points to same object
stringsCopy = strings; // allowed
Can we call any method of List<?>
? No, there are some restrictions.
List<Bulldog> bulldog = new ArrayList<>();
List<?> any = bulldog;
any.add(new Bulldog()); // error
any.add(new Dog()); // error
any.add("some string"); // error
Object o = any.get(1); // allowed
Once we assign a list to List, then we can’t call add(E element)
method as compiler doesn’t know the type of List<?>
. Only thing we can add to such list is a null.
In general, calling any method that uses type parameter triggers compilation error; The add(E e)
has type parameter E and it is not allowed. However, we are free to call methods such as E get()
, boolean remove(Object o)
or int size()
or clear()
etc., on List<?>
as there is no type parameter in these method’s parameters.
Can we call a method whose return type is type parameter? Yes, we can.
We can call method such as E get(int index)
or E remove(int index)
that has type parameter in return type.
List<Pet> pets = new ArrayList<>();
pets.add(new Pet());
List<?> wildList = pets;
Object obj = wildList.get(0);
Object removedObj = wildList.remove(0);
But, note that the type of object returned by such methods is Object as type E is unknown for List<?>
.
Let’s go through some methods List<E>
which are allowed on variable List<?>
List<Pet> pets = new ArrayList<>();
List<?> wildList = pets;
Collection<Pet> c = new ArrayList<>();
// not allowed, methods has type parameter E
wildList.add(1,new Pet()); // error
wildList.add(new Pet()); // error
wildList.addAll(c); // error
wildList.set(1, new Pet()); // error
// allowed, methods no type parameter
wildList.remove(1);
wildList.remove(new Pet());
wildList.removeAll(c);
wildList.retainAll(c);
Reason why they are allowed or not is as follows:
Method | Allowed | Reason |
---|---|---|
void add(int index, E element) | No | parameter has E (type parameter) |
boolean add(E element) | No | parameter has E |
boolean addAll(Collection<? extends E> c) | No | parameter has E |
E set(int index, E element) | No | parameter has E |
E remove(int index) | Yes | no E in parameter, even though return type is E |
boolean remove(Object o) | Yes | no E in parameter, note Object it not E |
boolean removeAll(Collection<?> c) | Yes | no E in parameter |
boolean retainAll(Collection<?> c) | Yes | no E in parameter |
The above restrictions are true for all generic types that uses wildcard ?; not just the List<?>
.
List<?> vs List<Object>
Remember that List<Object>
and List<?>
are not the same.
- we can assign only a list of Object to
List<Object>
, but we can assign list of any type toList<?>
- we can insert an Object or its subtype, into a
List<Object>
, but we can only insert null into a List.
Summary
- the
List<?>
means list of unknown type. - we can assign or pass list of any type to
List<?>
. - we can’t assign
List<?>
to any other list variable such asList<String>
. - we can’t call methods which uses type parameter such as
add(E element)
onList<?>
. - we are free to call methods such as
boolean remove(Object o)
orint size()
etc., onList<?>
as there is no type parameter in their parameters. - we can also call methods such as
E get(int index)
orE remove(int index)
that has type parameter in return type. The object returned by such methods is of type Object. - the
List<Object>
andList<?>
are not the same.
If you feel the List<Dog>
or List<String>
, that can accept list of only a specific type, as one extreme; the List<?>
, which accepts list of any type, as the other extreme, then can we define something in between?
Yes, Java Generics use extends and super keywords to fine tune the variable and parameter declarations. With these keywords we can define upper or lower bound of wildcards and next chapter explains its use.