Java Generics “capture of ?” Example
Java generics introduced type parameters to provide compile-time type safety. Wildcards such as ? are used when a method or a type doesn’t need to know the exact generic type. Sometimes the compiler reports a type named capture of ? (or similar) in error messages — that’s the compiler internally giving a name to the unknown type represented by a wildcard. Understanding capture conversion and helper methods lets you write safe code that manipulates collections with wildcards. Let us delve into understanding Java Generics capture of wildcards and how it ensures type safety while providing flexibility in handling different generic types.
1. Introduction
Java Generics introduced type parameters to ensure compile-time type safety and reduce runtime errors caused by improper type casting. Wildcards such as ? are particularly useful when you want to write flexible code that works with different generic types without knowing their exact type at compile time. Sometimes, developers encounter the term “capture of ?” in compiler messages — this refers to the compiler internally creating a temporary type variable for the unknown wildcard type. Understanding this concept, along with the use of helper methods, helps developers safely manipulate collections using generics and wildcards.
2. Code Example
This comprehensive Java example demonstrates capture of ?, PECS principle, wildcards, and type safety all in one program.
// File: GenericsAllInOne.java
import java.util.*;
public class GenericsAllInOne {
// ---------------------------------------------------
// 1. Capture of ? — using a helper method
// ---------------------------------------------------
public static void swapUnknown(List<?> list, int i, int j) {
// Cannot use list.set directly because element type is '?'
swapHelper(list, i, j); // Compiler performs capture conversion here
}
private static <T> void swapHelper(List<T> list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
// ---------------------------------------------------
// 2. PECS principle (Producer Extends, Consumer Super)
// ---------------------------------------------------
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T val : src) dest.add(val);
}
// ---------------------------------------------------
// 3. Arrays vs Generics (type safety)
// ---------------------------------------------------
public static void arrayVsGeneric() {
try {
Integer[] intArr = new Integer[2];
Object[] objArr = intArr; // Allowed (arrays are covariant)
objArr[0] = "oops"; // Runtime error (ArrayStoreException)
} catch (ArrayStoreException ex) {
System.out.println("ArrayStoreException caught: " + ex.getMessage());
}
// Generics are invariant (compile-time safe):
// List<Integer> li = new ArrayList<>();
// List<Object> lo = li; // Compile-time error
}
// ---------------------------------------------------
// 4. WildcardExamples — ? extends and ? super in action
// ---------------------------------------------------
static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
System.out.println("After adding Integers: " + list);
}
static void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println("Number: " + n);
}
}
// ---------------------------------------------------
// 5. Main method
// ---------------------------------------------------
public static void main(String[] args) {
// a) Capture of ?
List<String> words = new ArrayList<>(Arrays.asList("alpha", "beta", "gamma"));
System.out.println("Before swap: " + words);
swapUnknown(words, 0, 2);
System.out.println("After swap: " + words);
// b) PECS example
List<Integer> ints = Arrays.asList(10, 20, 30);
List<Number> nums = new ArrayList<>();
copy(nums, ints);
System.out.println("Numbers after copy: " + nums);
// c) Arrays vs Generics
arrayVsGeneric();
// d) WildcardExamples
System.out.println("--- Wildcard Examples ---");
addIntegers(nums); // List<Number> is ? super Integer
printNumbers(nums); // List<Number> is ? extends Number
printNumbers(ints); // List<Integer> is ? extends Number
}
}
2.1 Code Example
The GenericsAllInOne class provides a unified demonstration of various Java Generics concepts, including wildcard capture, PECS (Producer Extends, Consumer Super), type safety, and the differences between arrays and generics. In the first section, the swapUnknown() method accepts a List<?>, which cannot be modified directly because the element type is unknown. To handle this, it calls swapHelper(), a generic method with type parameter <T>, which enables element swapping after type capture conversion. This showcases the concept of “capture of ?”, where the compiler infers the actual type of the wildcard at runtime. The second part, the copy() method, demonstrates the PECS principle—“Producer Extends, Consumer Super.” Here, src is a producer of type <? extends T> (it provides elements), while dest is a consumer of type <? super T> (it accepts elements), allowing safe copying from a list of subtype elements into a list of supertypes. In the arrayVsGeneric() method, we observe the difference between arrays and generics in type safety. Arrays are covariant, meaning Integer[] can be assigned to Object[], but this can lead to a runtime ArrayStoreException when incompatible types are inserted. Generics, however, are invariant—List<Integer> cannot be assigned to List<Object>—ensuring type safety at compile time. The addIntegers() and printNumbers() methods demonstrate wildcard usage: ? super Integer allows adding integers safely to a list (as it can hold Integer or any of its supertypes), whereas ? extends Number allows reading from a list of numbers but not writing to it, except for null. Finally, the main() method ties everything together by showing practical examples—swapping elements in a string list, copying integers into a list of numbers, handling type safety in arrays, and running wildcard-based operations. The output clearly illustrates how generics prevent type-related runtime issues while maintaining flexibility and code reusability in Java.
2.2 Code Output
The program, when run, produces the following output:
Before swap: [alpha, beta, gamma] After swap: [gamma, beta, alpha] Numbers after copy: [10, 20, 30] ArrayStoreException caught: java.lang.String --- Wildcard Examples --- After adding Integers: [10, 20, 30, 1, 2] Number: 10 Number: 20 Number: 30 Number: 1 Number: 2 Number: 10 Number: 20 Number: 30
The output confirms each concept in action. The swap example proves wildcard capture works correctly, changing [alpha, beta, gamma] to [gamma, beta, alpha]. The PECS example shows copy() safely moving elements from integers to numbers. The array example throws an ArrayStoreException, showing arrays’ runtime type risk. Finally, wildcard examples print modified lists and demonstrate reading and writing rules for ? extends and ? super.
3. Conclusion
In conclusion, understanding Java Generics and wildcard capture is key to writing robust, type-safe, and reusable code. Wildcards like ? extends and ? super allow flexible method definitions while maintaining type safety. The concept of “capture of ?” helps the compiler safely handle unknown types by inferring them through helper methods. Additionally, comparing arrays and generics reveals why generics are safer — preventing runtime errors that arrays might allow. By mastering capture conversion, PECS, and wildcard principles, Java developers can confidently handle complex type hierarchies while keeping their code clean, reliable, and extensible.

