1. Overview
In this quick tutorial, we'll see the similarities and differences between <?> and <? extends Object> in Java Generics.
However, this being an advanced topic, it's imperative to get a basic understanding of the subject before we dive into the crux of the matter.
2. Background of Generics
Generics were introduced in JDK 5 to eliminate compile-time errors and strengthen type safety. This extra type-safety eliminates casting in some use cases and empowers programmers to write generic algorithms, both of which can lead to more readable code.
For example, pre-JDK 5, we'd have to work with the elements of a list using casting. This, in turn, created a certain class of runtime errors:
List aList = new ArrayList();
aList.add(new Integer(1));
aList.add("a_string");
for (int i = 0; i < aList.size(); i++) {
Integer x = (Integer) aList.get(i);
}
Now, this code has two issues we'd like to address:
- We need an explicit cast to extract values from aList – the type depends on the variable type on the left – Integer in this case
- We'll get a runtime error on the second iteration when we're trying to cast a_string to an Integer
Generics fill the role for us:
List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add("a_string"); // compile time error
for (int i = 0; i < iList.size(); i++) {
int x = iList.get(i);
}
The compiler will tell us that it's not possible to add a_string to a List of type Integer, which is better than finding out at runtime.
Moreover, no explicit casting is needed since the compiler already knows that iList holds Integers. Additionally, due to the magic of unboxing, we didn't even need an Integer type, its primitive form is enough.
3. Wildcards in Generics
A question mark, or wildcard, is used in generics to represent an unknown type. It can have three forms:
- Unbounded Wildcards: List<?> represents a list of unknown type
- Upper Bounded Wildcards: List<? extends Number> represents a list of Number or its sub-types such as Integer and Double
- Lower Bounded Wildcards: List<? super Integer> represents a list of Integer or its super-types Number and Object
Now, since Object is the inherent super-type of all types in Java, we would be tempted to think that it can also represent an unknown type. In other words, List<?> and List<Object> could serve the same purpose. But they don't.
Let's consider these two methods:
public static void printListObject(List<Object> list) {
for (Object element : list) {
System.out.print(element + " ");
}
}
public static void printListWildCard(List<?> list) {
for (Object element: list) {
System.out.print(element + " ");
}
}
Given a list of Integers, say:
List<Integer> li = Arrays.asList(1, 2, 3);
printListObject(li) will not compile, and we'll get this error:
The method printListObject(List<Object>) is not applicable for the arguments (List<Integer>)
Whereas printListWildCard(li) will compile and will output 1 2 3 to the console.
4. <?> and <? extends Object> – The Similarities
In the above example, if we change the method signature for printListWildCard to:
public static void printListWildCard(List<? extends Object> list)
It would function in the same way as printListWildCard(List<?> list) did. This is due to the fact that Object is a supertype of all Java objects, and basically everything extends Object. So, a List of Integers gets processed as well.
In short, it means that ? and ? extends Object are synonymous in this example.
While in most cases that would hold true, but there are a few differences as well. Let's look at them in the next section.
5. <?> and <? extends Object> – The Difference
Reifiable types are those whose type is not erased at compile time. In other words, a non-reifiable type's runtime representation will have less information than its compile-time counterpart, because some of it'll get erased.
As a general rule, parameterized types are not reifiable. This means List<String> and Map<Integer, String> are not reifiable. The compiler erases their type and treats them as a List and Map respectively.
The only exception to this rule is unbounded wildcard types. This means List<?> and Map<?,?> are reifiable.
On the other hand, List<? extends Object> is not reifiable. While subtle, this is a notable difference.
Non-reifiable types cannot be used in certain situations such as in an instanceof operator or as elements of an array.
So, if we write:
List someList = new ArrayList<>();
boolean instanceTest = someList instanceof List<?>
This code compiles and instanceTest is true.
But, if we use the instanceof operator on List<? extends Object>:
List anotherList = new ArrayList<>();
boolean instanceTest = anotherList instanceof List<? extends Object>;
then line 2 does not compile.
Similarly, in the below snippet, line 1 compiles, but line 2 doesn't:
List<?>[] arrayOfList = new List<?>[1];
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]
6. Conclusion
In this short tutorial, we saw the similarities and differences in <?> and <? extends Object>.
While mostly similar, there are subtle differences between the two in terms of their being reifiable or not.