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.