Wildcards

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 5 min

TL;DR

  • In Generics, the question mark ? represents a Wildcard (an unknown type).
  • It is used in variable assignments and method arguments, not in class/method declarations.
  • There are three types of wildcards: Unbounded (?), Upper Bounded (? extends T), and Lower Bounded (? super T).

Concept

A common misunderstanding in Java is that if Integer is a subtype of Number, then List<Integer> must be a subtype of List<Number>. This is false. Generics are invariant. A List<Integer> is not a List<Number>.

Wildcards solve this problem by allowing flexibility in method parameters and variable assignments.

Types of Wildcards

  1. Unbounded <?>: Means “a list of anything”. You can read from it (as Object), but you cannot safely add anything to it.
  2. Upper Bounded <? extends Number>: Means “a list of Number, or any of its subclasses (Integer, Double)”. You can safely read Numbers from it, but you cannot add to it.
  3. Lower Bounded <? super Integer>: Means “a list of Integer, or any of its superclasses (Number, Object)”. You can safely write Integers into it.

Examples

import java.util.ArrayList;
import java.util.List;

public class WildcardExample {

    // Unbounded Wildcard
    public static void printList(List<?> list) {
        for (Object elem : list) {
            System.out.print(elem + " ");
        }
        System.out.println();
    }

    // Upper Bounded Wildcard (Read-Only)
    public static double sum(List<? extends Number> list) {
        double total = 0;
        for (Number n : list) {
            total += n.doubleValue();
        }
        // list.add(10); // COMPILE ERROR! You don't know what specific list this is.
        return total;
    }

    // Lower Bounded Wildcard (Write-Only)
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
        // Number n = list.get(0); // ERROR! You only know it's at least an Object.
    }

    public static void main(String[] args) {
        List<Integer> ints = new ArrayList<>(List.of(1, 2, 3));
        List<Double> doubles = new ArrayList<>(List.of(1.1, 2.2));
        
        printList(ints);
        
        System.out.println("Sum ints: " + sum(ints));
        System.out.println("Sum doubles: " + sum(doubles));
        
        List<Number> nums = new ArrayList<>();
        addNumbers(nums); // Valid, because Number is a superclass of Integer
    }
}

Interview Questions

Q: What is the difference between List<Object> and List<?>?
A: List<Object> is a specific list that holds objects of type Object (or any subclass). You can add anything to it. You cannot pass a List<String> to a method expecting a List<Object>.
List<?> is a wildcard list of an unknown type. You can pass a List<String> or List<Integer> to a method expecting List<?>. However, because the compiler doesn’t know the exact type, you are strictly forbidden from adding anything to a List<?> (except null).

Q: Why can’t you add elements to a List<? extends Number>?
A: If you have a method parameter List<? extends Number>, you could pass a List<Double> to it. If the compiler allowed you to write list.add(new Integer(5)) inside that method, you would be corrupting the original List<Double> by inserting an Integer. To prevent this, the compiler makes ? extends collections strictly read-only.