Gidhub BE Developer

Java Generics에서 <?> 와 <? extends Object> 차이에 대해 알아보자



  • we’ll see the similarities and differences between <?> and <? extends Object> in Java Generics.

Wildcards(=?) in Generics

  • A question mark, or wildcard, is used in generics to represent an unknown type.

  • It can have three forms:

1. Unbounded Wildcards
  List<?> represents a list of unknown type

2. Upper Bounded Wildcards 
  List<? extends Number> represents a list of Number or its sub-types such as Integer and Double

3. 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> list = Arrays.asList(1, 2, 3);
  • printListObject(list) will not compile, and we’ll get this error:
java: incompatible types: java.util.List<java.lang.Integer> cannot be converted to java.util.List<java.lang.Object>
  • Whereas printListWildCard(list) will compile and will output 1 2 3 to the console.


  • 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.


  • 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.

  • So we can summary it like this

## Reifiable
List<?> and Map<?,?> are reifiable.

## Not Reifiable
List<String> and Map<Integer, String>
List<? extends Object>
  • 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:

// Success
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>:

// Fail
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

// Success
List<?>[] arrayOfList = new List<?>[1];
// Fail
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]


  • While mostly similar,

    there are subtle differences between the two in terms of their being reifiable or not.