How to tame the wildcard in Java

- select the contributor at the end of the page -

The wildcard in Java can be downright confusing. From the syntax, it’s easy enough to guess that "? extends T" means that there's a class that extends the generic type T, but what about "? super T"? And why on earth would we ever want to use these features? Let’s answer these questions with a code-driven explanation. When we’re done, you should have a better handle on how to use wildcards (and why you’d ever want to use them in the first place).

The problem

Let’s suppose we’re writing a staff management application. We’ve got a Person class in our domain, and it has two subclasses: an Employee and a Partner. One common operation you might wish to perform is to save these objects into a database, and maybe write a method like this:

public void saveAll(final List<Person> people)

{

    // Saves people into a database ...

}

What could go wrong with that? It’s so nice and simple, and we can use it like this:

List<People> people = new ArrayList<>();

people.add(donDraper);

people.add(peggyOlson);

saver.saveAll(people);

But wait. Let’s step back for a minute. We actually want to be able to save more than just lists of people. We have Employees and Partners as well, and we want to be able to save lists of each. If we try to write this code, we get a compile error on the line highlighted in red.:

List<Partner> people = new ArrayList<>();

people.add(donDraper);

people.add(bertCooper);

saver.saveAll(people);

That sucks.

Wildcards to the rescue

Wildcards let us increase the flexibility of our generic type parameters by allowing us to pass in subclasses and superclasses. You can spot them in code thanks to the way they look (? extends Foo or ? super Bar). We could change the original method’s signature like this:

public void saveAll(final List<? extends Person> people)

{

    // Saves people into a database ...

}

And now we can pass in variables that have types like List<Partner> or List<Employee>, as well as List<Person> as an argument to our saveAll method. Problem solved! It’s also worth noting that extends doesn’t just allow us to use subclasses of a class, but also implementers of an interface as well.

If you hear people refer to the bound of a wildcard, they’re talking about the type that you’re extending (in this example it’s Person). Another term you’ll often hear in the context of generics is covariance. We call List<? extends Person> covariant because it preserves the subtyping relationship, or variance, of the Person type hierarchy when it’s a generic parameter of a List.

What about super?

Now that we’ve covered why you might want to use the extends form of wildcards, let’s move on to the super form of wildcards. Instead of saying “It’s OK to have a subclasses or implementation here”, the super form lets us say “It’s OK to have a superclass or an interface that’s implemented.” In other words, it reverses the relationship with the bound on the wildcard. It’s a bit less obvious why we would want to use super, but it makes sense in certain situations.

If we needed a method that imports new partners for our business from a CSV file, we might write something like this:

public void importInto(final File file, final List<Partner> people)

{

// Imports people from a new csv file...

}

So, we take a parameter that is our list of all the partners we’re maintaining, and we’ll add new Partner elements into it. Now that works fine if you want to write code like this:

// List of all the partners

List<Partner> partners = new ArrayList<>();

// elsewhere in the codebase, when importing a new file of partners

loader.importInto(file, partners);

This just passes a list of partner objects as a parameter, but that’s not all that we want to do. It’s a totally legitimate thing to want to do to store a Partner in a List<Person> or some other parent type. But if we write this code, it will fail to compile on the red line.

// List of all the people in the firm

List<Person> people = new ArrayList<>();

// elsewhere in the codebase, when importing a new file of partners

loader.importInto(file, people);

We have the same problem as before, a List<Partner> is just a List<Partner> -- it’s just not flexible enough. This is where the lower bounded wildcards with the super keyword come into play. We can change our method signature to the following and our code will compile:

public void importInto(final File file, final List<? super Person> people)

This variable is contravariant: List<T> subtypes List<? super Person> if the T is a super type of Person.

Safety last

You now know how to increase the flexibility of generic type parameters by using wildcards, but when should you use contravariance/super vs covariance/extends? More importantly, when is it safe to do so?

There’s a very simple principle to remember here. If you’re only planning on putting values into the variable of T, use ? super T. If you’re only planning on removing values, use ? extends T. If you want to do both, then it’s not safe to use wildcards. In our importing example we were going to put the Person instances into our people argument once we imported them out of the database, so it was safe to use super. In our saveAll example we were only getting values out of our people parameter, making it safe to use extends.

Takeaway

There are many more topics to explore on this subject (we haven’t explained why Java’s arrays are unsafe due to covariance or talked about the fact that you have generic relationships with multiple bounds. But you should now have a better handle on why wildcards can be useful, and when to use the two different types of wildcards safely. If you’re looking to learn more about Java Generics, check out my complete Java Generics course.

Get our content first. In your inbox.

Contributor

Richard Warburton

is an empirical technologist and solver of deep-dive technical problems and works independently as a Software Engineer and Trainer. Recently he has written a book on Java 8 Lambdas for O’Reilly and helps developers learn via ITERATR LEARNING and Pluralsight. He’s worked as a developer in diverse areas including Statistical Analytics, Static Analysis, Compilers and Network Protocols. He is a leader in the London Java Community. Richard is also a known conference speaker and has presented at Devoxx, JavaOne, QCon SF, JFokus, Devoxx UK, Geecon, Oredev, JAX London, JEEConf and Codemotion. He obtained a PhD in Computer Science from The University of Warwick.