DEV Community

Cover image for Unbounded Wildcards - [OOP & Java #10]
Liu Yongliang
Liu Yongliang

Posted on • Originally published at tlylt.Medium

Unbounded Wildcards - [OOP & Java #10]

I discussed generics and bounded wildcards in the below article.

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:

  1. What is the difference between List<?> and List<Object> ?
  2. What is the difference between List<?> and List<? extends Object> ?
  3. Can return types be ? or List<?>?
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<?> and List<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>();
Enter fullscreen mode Exit fullscreen mode

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>());
Enter fullscreen mode Exit fullscreen mode
  • The method test can take in a list of any type
  • The method test2 can only take in a list of type Object

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 or List.clear.

Question 2

What is the difference between List<?> and List<? 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>());
Enter fullscreen mode Exit fullscreen mode

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 becomes List.
  • List<?> is reifiable, during runtime it becomes List<?>.
// 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>;
Enter fullscreen mode Exit fullscreen mode

I don't think this difference is significant, but it does not hurt to know more:)


Question 3

Can return types be ? or List<?>?

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() {
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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.


Reference

Top comments (0)