Java Generics Wildcards

A Wildcard Type in Java represents an unknown type. Wildcards are commonly used when working with Java collection classes. Wilcards are indicated with the ? Symbol.

In this tutorial we’ll examine wildcards in Java generics, and examine how they help us to assign and cast collections of different types.

Collections And Inheritance

As you know, every Java object is a descendant from java.lang.Object.

1
2
String str = "This is a String";
Object obj = str;

Moreover, consider we have the following types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class Variable {
    protected String name;
    protected Integer value;

    public Variable(String name, Integer value) {
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public Integer getValue() {
        return value;
    }
}

public class X extends Variable {
    public X(Integer value) {
        super("X", value);
    }
}

public class Y extends Variable {
    Y(String name, Integer value) {
        super(name, value);
    }

    public Y(Integer value) {
        super("Y", value);
    }
}

public class Z extends Y {
    public Z(Integer value) {
        super("Z", value);
    }
}

Above we define three classes a base class Variable and the classes X, Y, and Z which are subclasses of Variable. Java allows us to perform Reference Casting between these types.

1
2
X variableX = new X();
Variable variable = variableX;

As you can see we can easily assign a type to a variable of an ancestor type.

However, things get a little tricky when working with collections.

Consider we have the collections:

1
2
3
4
List<Variable> listOfVariables = new ArrayList<>();
List<X> listOfXVariables = new ArrayList<>();
List<Y> listOfYVariables = new ArrayList<>();
List<Z> listOfZVariables = new ArrayList<>();

You might expect that since X is a subclass of Variable we can assign a List<X> to a List<Variable>:

1
2
listOfVariables = listOfXVariables; //compile error
listOfXVariables = listOfVariables; //compile error

However, attempting to perform this operation results in compile time errors. These operations are not allowed, as it would be possible to assign unrelated types into a collection that expects a different type.

For example, if listOfVariables contained variables of type Y. Then we would be assigning variables of Type Y to a List<X>. For these reasons casting is also not allowed.

1
2
listOfVariables = (List<Variable>)  listOfXVariables; //compile error
listOfXVariables = (List<X>) listOfVariables; //compile error

Let’s now look at how we can workaround this problem.

The Need For Wildcards

It’s very common in Java to create reusable methods, that work on collections of a specific type.

1
2
3
4
5
public static void printVariables(List<Variable> variables) {
    for(Variable var : variables) {
        System.out.println(var.getName());
    }
}

Above we create a method to print the names of Variable instances contained in a List. We can call this method with a List<Variable>:

1
printVariables(listOfVariables);

However, if we try to call this method with the other lists of Variable subclasses we would receive compile time errors:

1
2
3
printVariables(listOfXVariables); //compile error
printVariables(listOfYVariables); //compile error
printVariables(listOfZVariables); //compile error

We can solve this problem through the use of generic wildcards. Generic wildcards allow us to:

  • Read from a generic collection
  • Insert into a generic collection.

Java provides us with three ways to create generic collections using a wildcard.

  • Unknown Wildcard – List<?> unknownList = new ArrayList<>();
  • Upper Bound Wildcard – List<? extends Variable> variableList = new ArrayList<>();
  • Lower Bound Wildcard – List<? super Y> yVariableList = new ArrayList<>();

Unknown Wildcard

The unknown wildcard is used to describe an unknown type. For example if we declare a List with an unknown wildcard:

1
List<?> objects = new ArrayList<Variable>();

Since, we don’t know the exact type of the elements in the objects list, we can only refer to the elements as java.lang.Object types. For example:

1
2
3
4
5
public static void printObjects(List<?> objects) {
    for(Object obj : objects) {
        System.out.println(obj);
    }
}

However, since we used the unknown wildcard in the above method, we can now call this method with any list of objects.

1
2
3
4
printObjects(listOfVariables);
printObjects(listOfXVariables);
printObjects(listOfYVariables);
printObjects(listOfZVariables);

But, we cannot insert any objects into this List because it’s of an unknown type.

1
2
3
objects.add(new X(5)); //compile error
objects.add(new Y(2)); //compile error
objects.add(new Z(2)); //compile error

Wildcard Upper Bound

A wildcard upper bound allows us to define a collection that can contain elements of the bound type or subclasses of the bound. To define an upper bound we can use the extends keyword.

1
List<? extends Variable> variableList = new ArrayList<>();

Above we have a list that can contain elements of type Variable, including it’s subtypes. Let’s use the upper bound to improve the printVariables method we created earlier.

1
2
3
4
5
public static void printVariables(List<? extends Variable> variables) {
    for(Variable var : variables) {
        System.out.println(var.getName());
    }
}

Great, we can now call this method with collections of type Variable or a subclass of Variable.

1
2
3
4
printVariables(listOfVariables);
printVariables(listOfXVariables);
printVariables(listOfYVariables);
printVariables(listOfZVariables);

But, once again we cannot insert elements into this collection.

1
variableList.add(new X(5)); //compile error

Lower Bound Wildcard

We saw in the previous sections that we can’t insert into a list with an unknown wildcard, or an upper bound wildcard.

Whereas a lower bound wildcard allows us to insert. A lower bound is defined with the super keyword.

1
List<? super Y> yVariableList = new ArrayList<>();

This signifies that the list yVariableList can contain types of type Y or a subclasses of type Y. It is safe to insert because we can be sure that elements in the list are of type Y.

1
2
yVariableList.add(new Y(5));
yVariableList.add(new Z(5));

But, we cannot insert a variable of type X because it is not a subclass of Y.

1
yVariableList.add(new X(5)) //compile error;

However, we can only assign a collection to a collection with a lower bound, if the collection has a type that is a superclass of the bound.

1
2
3
yVariableList = listOfVariables;

yVariableList = listOfZVariables; //compile error

Moreover, if we update the printVariables method to:

1
2
3
4
5
public static void printVariables(List<? super Y> variables) {
    for(Object var : variables) {
        System.out.println(var);
    }
}

We can only read the elements if we use them as Object types. This is because the elements in the list could be of any type when the printVariables method is called.

Consequently, if we read an element from the list we must assign it to a type of java.lang.Object

1
2
3
4
Object var = yVariableList.get(0);
Variable varOfVariable = yVariableList.get(0); //compile error;
Y varY =  yVariableList.get(0); //compile error;
Z varZ =  yVariableList.get(0); //compile error;

However, we can still perform a cast.

1
2
3
Variable varOfVariable = (Variable) yVariableList.get(0);
Y varY = (Y)  yVariableList.get(0);
Z varZ = (Z) yVariableList.get(0);

Conclusion

In this short tutorial we examined Wildcard type parameters in Java. We examine unknown, upper and lower bound wildcards.