Wildcards in Java - or the question mark type
A few days ago I spent time improving the configurability of Elis, a software platform that is built in Java and uses the OSGI framework. OSGI has many quirks and is not always the most straightforward when it comes to working with it (I can in particular rant about the tool support for managing dependencies). Anyway, a part of the configuration framework returned a particular type of Java that I hadn’t worked with, even less understood, before: the wildcard type.
Lets start with an example. To retrieve a configuration you do the following:
Configuration config = configAdmin.getConfiguration(SERVICE_PID);
Directory<String, ?> properties = config.getProperties();
? doing there and what does it mean? Turns out it’s more generic than using
Object and the Java documentation says it represents and “unknown type”. You may use it to, for example, write more flexible methods that can operate on a range of types.
using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards
Now, besides this remark from the Java docs, how does it actually work? Here are four ways to iterate over a list of objects or unknown types.
The first two methods are self-explanatory. Without casting explicitly to an
process1(...) we cannot use any methods from the
Item interface, but our
items list may contain any type as long as it inherits from
process2 we are more restrictive and only allow
items to contain objects of type
Item which means we can use it directly as such.
The third case wasn’t immediately clear to me. It turns out you can iterate over
Objects too and the list can contain any object too (as long as they are the same?) Why is that useful and what’s the difference? The bytecode of the two functions (I pasted one of them below) are identical:
javap comes with the JDK and enables you too look at compiled classes’ bytecode.)
You cannot, however, instantiate a list with the generic type
<?> and this is because the compiler cannot know which type the objects in the list will be. Another way of looking at it is that the method can accept anything, but the caller must always know (at least something) about the type. Hence, these three are legal
new ArrayList<Item>(), and
<?> type on its own may not make a lot of sense in practice (unless you have some way of safely processing any type of object inheriting from
Object). Therefore it is possible to limit the possibilities of the wildcard type. Java calls this bounds. In
process4(List<? extends Item>) the list may contain any type which is a subtype of
Item and it is called “upper bounded”. This means that if you are trying to pass
new ArrayList<Object>() the compiler will yell at you. Conversely, if the method argument was
process5(List<? super Item>), then
new ArrayList<SpecificItem>() is illegal. This is called, not surprisingly, “lower bounded” wildcards.
I’ve yet to use myself in Java, but at least OSGI taught me something new. On a final note, digging into the wildcard type felt much easier after having worked with Scala’s type system. Win!