Skip to main content

Wildcards in Generics

In generic code, the question mark (?), called the wildcard, represents an unknown type.

Wildcards are particularly useful as the type of a parameter in a method. They allow you to write incredibly flexible code that can operate on collections of different types without knowing the exact type in advance.


1. Unbounded Wildcards (?)

The unbounded wildcard <?> stands for any type. It is useful when you are writing a method that can be implemented using functionality provided in the Object class, or when the code is independent of the type parameter (like List.size() or List.clear()).

Example

Imagine you want to write a method that prints any type of List.

import java.util.Arrays;
import java.util.List;

public class LabWildcard1 {

// Method accepts a List of ANY type
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}

public static void main(String args[]) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("A", "B", "C");

// Both calls are completely valid!
printList(intList);
printList(strList);
}
}

[!WARNING] While List<?> can accept any list, you cannot add any elements to a List<?> (except null). Because the compiler doesn't know what type the list actually holds, it prevents you from adding anything to ensure type safety.


2. Upper Bounded Wildcards (? extends T)

You use an upper bounded wildcard to relax the restrictions on a variable. Suppose you want to write a method that works on List<Integer>, List<Double>, and List<Number>. You can achieve this by using an upper bounded wildcard: <? extends Number>.

This means the method accepts a list of Number or any subclass of Number.

Example

import java.util.Arrays;
import java.util.List;

public class LabWildcard2 {

// Accepts a List of Number, or any subclass (Integer, Double, Float...)
public static double sum(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue(); // Safe because we know it's at least a Number
}
return sum;
}

public static void main(String args[]) {
List<Integer> ints = Arrays.asList(1, 2, 3);
System.out.println("Sum of ints: " + sum(ints));

List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
System.out.println("Sum of doubles: " + sum(doubles));

List<String> strings = Arrays.asList("1", "2");
// sum(strings); // COMPILE ERROR! String does not extend Number.
}
}

3. Lower Bounded Wildcards (? super T)

The lower bounded wildcard restricts the unknown type to be a specific type or a super class of that type. It is expressed using the super keyword: <? super T>.

This is most often used when you want a method to be able to safely insert elements into a collection.

Example

Suppose you want to write a method that adds Integer objects to a list. To maximize flexibility, you want the method to work on List<Integer>, List<Number>, and List<Object> (anything that can hold an Integer).

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

public class LabWildcard3 {

// Accepts a List of Integer, or any superclass of Integer (Number, Object)
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i); // Safe! Because a List of Number/Object can definitely hold Integers.
}
System.out.println("List after addition: " + list);
}

public static void main(String args[]) {
List<Integer> intList = new ArrayList<>();
addNumbers(intList);

List<Number> numList = new ArrayList<>();
addNumbers(numList);

List<Object> objList = new ArrayList<>();
addNumbers(objList);

List<Double> doubleList = new ArrayList<>();
// addNumbers(doubleList); // COMPILE ERROR! Double is NOT a superclass of Integer.
}
}