I discussed generics and bounded wildcards in the below article.
Bounded Generic Types & Wildcards - [OOP & Java #6]
Liu Yongliang ・ Sep 22 '20 ・ 8 min read
While I covered most of the basics about generics and wildcards, there are still areas requiring further attention and discussion. In this article, I would like to pen down some subtleties with regard to unbounded wildcards.
Motivation
Some common questions around this topic are:
- What is the difference between
List<?>
andList<Object>
? - What is the difference between
List<?>
andList<? extends Object>
? - Can return types be
?
orList<?>
?
p.s. While I used List<?>
in my examples, I am referring to all possible generic classes, such as custom a generic class named ImmutableList<?>
.
Question 1
What is the difference between
List<?>
andList<Object>
?
When we talk about wildcards and generics, our focus is always on type and type safety. The purpose of having these implementations is to allow for polymorphism.
When we have wildcards, we can think of the word "any". So List<?>
is a list of any possible type. The type is unknown, but we know that any type can replace that ?
and the expression will be a valid one. This is not the case with List<Object>
. Due to invariance, the subtyping relationship like the following does not hold for generics:
// This works
Number[] myArr = new Integer[]();
// This does not work
List<Number> myDemo = new List<Integer>();
If they appear in a parameter declaration, there are some obvious differences:
// example method declaration
void test(ArrayList<?> myList) {}
// example method declaration
void test2(ArrayList<Object> myList) {}
// this works
test(new ArrayList<Integer>());
// this works
test(new ArrayList<Object>());
// this does not work
test2(new ArrayList<Integer>());
// this works
test2(new ArrayList<Object>());
- The method
test
can take in a list of any type - The method
test2
can only take in a list of typeObject
List<?>
might be useful when:
- Only functionalities provided in the
Object
class are being used. - Operations do not depend on the type parameter, such as static methods of
List
:List.size
orList.clear
.
Question 2
What is the difference between
List<?>
andList<? extends Object>
?
I was not aware of the above until I researched unbounded wildcards.
- List<?> is unbounded, means a list of any type
- List<? extends Object> is bounded, means a list of any type that extends from Object. Given that all classes in Java are subclasses of the Object class, any type can be used here as well.
Looking at the previous example, they both seem to serve the same purpose:
void test(ArrayList<?> myList) {}
void test2(ArrayList<? extends Object> myList) {}
// this works
test(new ArrayList<Integer>());
// this works
test(new ArrayList<Object>());
// this works
test2(new ArrayList<Integer>());
// this works
test2(new ArrayList<Object>());
The one difference mentioned in the article Java Generics is the following:
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. The only exception to this rule is unbounded wildcard types.
Generic types are erased during runtime. This means List<Integer>
becomes the raw type List
after compile time. However, List<?>
will remain as List<?>
after compile time. If we have information about the generic type, we can apply instanceof
on it.
So,
-
List<? extends Object>
is not reifiable, during runtime it becomesList
. -
List<?>
is reifiable, during runtime it becomesList<?>
.
// this compiles
// allowed to use instanceof on unbounded wildcards
List aList = new ArrayList<Integer>();
aList instanceof List<?>;
// this does not compile
// not allowed to use instanceof on bounded wildcards
aList instanceof List<? extends Object>;
I don't think this difference is significant, but it does not hurt to know more:)
Question 3
Can return types be
?
orList<?>
?
According to Oracle's Java tutorial,
Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
So, it is possible but not recommended. Actually, there is a problem with the return type being ?
or List<?>
.
- For
?
There will be compile time error if we declare our return type to be ?
test.java:27: error: illegal start of type
public ? test() {
error: invalid method declaration; return type required
public ? test() {
- For
List<?>
Supposed we do have such a method that return such an object, what target type can we use to receive it?
// method declaration
public List<?> test() { return new ArrayList<Integer>();}
// this does not work
// error: incompatible types: java.util.List<capture#1 of ?>
// cannot be converted to java.util.List<java.lang.Integer>
List<Integer> ls = test();
// this works but may be of little use
List<?> ls = test(); // does compile
Even if we know that the returned object is actually List<Integer>
, we cannot assign List<?>
to any concrete types such as List<Integer>
. Therefore we also cannot invoke any Integer
related methods on the items within the list. We gain the benefits of using generic wildcards as a black box, but we lose the ability to manipulate it afterward.
Top comments (0)