Definition
- List> – unbounded wildcard meaning "list of some unknown type"; elements come out as Object; only null can be added
- Write restriction – the compiler blocks
list.add(x)because if ? is Integer, adding a String would corrupt the list; the compiler cannot verify safety - Capture helper – a private method with
that receivesListinstead ofList>; the compiler captures the wildcard as T at the call site - Inside the helper – T is a concrete type, so both
list.get()(returns T) andlist.set(i, value)(accepts T) are type-safe - JDK usage – Collections.swap, Collections.fill, and other utility methods use this pattern internally
At National Bank of India, List> is like a sealed envelope containing a list of some type of financial document. You can peek inside (read as Object), but you cannot insert a new document because you do not know whether the envelope contains cheques or loan forms. The capture helper is like asking a trusted clerk (generic method) to open the envelope. The clerk sees the actual document type (T) and can safely read and insert documents of that type.
Understanding the Concepts
What is wildcard capture?
When a method accepts List>, it can read elements as Object but cannot add elements (except null). This is because the compiler does not know what ? represents. The wildcard capture pattern solves this by delegating to a private generic method where the compiler captures ? as a named type parameter T.
Why can you not write to List>?
The compiler blocks writes to List> because it cannot guarantee type safety. The list might be a List
package com.bank.demo;
import java.util.List;
import java.util.ArrayList;
public class WildcardWriteRestrictionDemo {
static void process(List<?> list) {
// READ: OK, elements come out as Object
Object first = list.get(0);
System.out.println("Read: " + first);
// WRITE: blocked by compiler
// list.add("test"); // COMPILE ERROR
// list.add(42); // COMPILE ERROR
list.add(null); // OK: null is always safe
// Size, isEmpty, contains: all OK
System.out.println("Size: " + list.size());
}
public static void main(String[] args) {
List<String> accounts = new ArrayList<>(List.of("SAV2001", "CUR3001"));
process(accounts);
List<Integer> amounts = new ArrayList<>(List.of(10000, 25000));
process(amounts);
}
}
Read: SAV2001
Size: 2
Read: 10000
Size: 2
How does the capture helper pattern work?
The pattern involves two methods. The public method accepts List> and delegates to a private generic helper that accepts List. At the call site, the compiler infers T from the actual argument type, capturing the wildcard.
package com.bank.demo;
import java.util.ArrayList;
import java.util.List;
public class CaptureHelperDemo {
// Public method with wildcard: cannot do set() directly
public static void swap(List<?> list, int i, int j) {
// list.set(i, list.get(j)); // COMPILE ERROR: capture#1 of ?
swapHelper(list, i, j); // delegate to capture helper
}
// Private capture helper: compiler captures ? as T
private static <T> void swapHelper(List<T> list, int i, int j) {
T temp = list.get(i); // read as T: OK
list.set(i, list.get(j)); // write T: OK
list.set(j, temp); // write T: OK
}
public static void main(String[] args) {
List<String> accounts = new ArrayList<>(List.of("SAV2001", "CUR3001", "FD4001"));
System.out.println("Before swap: " + accounts);
swap(accounts, 0, 2);
System.out.println("After swap: " + accounts);
}
}
Before swap: [SAV2001, CUR3001, FD4001]
After swap: [FD4001, CUR3001, SAV2001]
How does the JDK use wildcard capture?
The JDK uses this pattern in several utility methods. Here is a practical example similar to Collections.reverse.
package com.bank.demo;
import java.util.ArrayList;
import java.util.List;
public class ReverseDemoWithCapture {
public static void reverse(List<?> list) {
reverseHelper(list);
}
private static <T> void reverseHelper(List<T> list) {
int size = list.size();
for (int i = 0; i < size / 2; i++) {
T temp = list.get(i);
list.set(i, list.get(size - 1 - i));
list.set(size - 1 - i, temp);
}
}
public static void main(String[] args) {
List<String> accounts = new ArrayList<>(List.of("SAV2001", "CUR3001", "FD4001"));
reverse(accounts);
System.out.println("Reversed: " + accounts);
}
}
Reversed: [FD4001, CUR3001, SAV2001]
What is the difference between List> and List
| Feature | List> | List
package com.bank.demo;
import java.util.ArrayList;
import java.util.List;
public class WildcardVsObjectDemo {
static void wildcardMethod(List<?> list) {
// list.add("test"); // COMPILE ERROR
}
static void objectMethod(List<Object> list) {
list.add("test"); // OK: List<Object> accepts Object
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>(List.of("SAV2001"));
wildcardMethod(strings); // OK: List<?> accepts any List
// objectMethod(strings); // COMPILE ERROR: List<String> != List<Object>
}
}
What are common mistakes?
Mistake: Trying to write to List> directly
// WRONG: cannot add to List<?>
static void addDefault(List<?> list) {
list.add("DEFAULT"); // COMPILE ERROR
}
// CORRECT: use capture helper
static void addDefault(List<?> list) {
addDefaultHelper(list);
}
private static <T> void addDefaultHelper(List<T> list) {
// This still does not work for adding a String to List<T>
// because T might not be String.
// The real fix depends on the use case.
}
The key insight is that the capture helper does not magically let you add arbitrary types. It lets you perform operations that are inherently type-safe (like swapping two elements from the same list) by giving the wildcard a name.
What should a senior engineer know?
- Capture errors in compiler output. The error message "capture#1 of ?" means the compiler tried to capture the wildcard but could not resolve it to a concrete type for the operation.
- OpenJDK implementation. Some JDK methods (like Collections.swap) use raw types internally instead of the capture helper for historical reasons. New code should use the generic helper pattern.
- Capture with bounded wildcards. The pattern works with
? extends Tand? super Tas well. The helper captures the exact bound. - Design guideline. If a public API uses wildcards, keep the implementation in a private generic helper. This gives callers maximum flexibility while maintaining internal type safety.
What are common interview follow-up questions?
- "Why can you not add to List>?" : The compiler does not know the actual type of ?, so it cannot verify that any addition is type-safe.
- "What is the only thing you can add to List>?" : null, because null is assignment-compatible with every reference type.
- "What is the capture helper pattern?" : A private generic method that captures the wildcard as a named type parameter, enabling type-safe operations.
- "Does the JDK use this pattern?" : Yes. Collections.swap, Collections.fill, and Collections.copy use capture helpers internally.
Visual Overview
Wildcard Capture Pattern ======================== Public method: Private capture helper: ┌──────────────────────────┐ ┌──────────────────────────────┐ │ void swap(List<?> list) │ │ <T> void helper(List<T> list)│ │ │ │ │ │ list.get(0) -> Object │ │ list.get(0) -> T │ │ list.set(0, x) -> ERROR │ ────▶ │ list.set(0, x) -> OK │ │ │ │ │ │ Compiler does not know ? │ │ Compiler captures ? as T │ └──────────────────────────┘ └──────────────────────────────┘ How capture works: ┌────────────────────────────────────────────┐ │ swap(myStringList, 0, 1) │ │ │ │ List<?> list = myStringList (? = String) │ │ swapHelper(list, 0, 1) │ │ -> compiler infers T = String │ │ -> swapHelper(List<String> list, 0, 1) │ │ -> T temp = list.get(0) -> String │ │ -> list.set(0, list.get(1)) -> OK │ └────────────────────────────────────────────┘
Key Takeaway
List> is a list of some unknown type. You can read elements from it as Object, but you cannot add anything except null because the compiler does not know the actual element type. If the list is really a List
The wildcard capture helper pattern solves this limitation. By creating a private generic method with a named type parameter T, the compiler captures the wildcard as T at the call boundary. Inside the helper, T is a concrete type, enabling type-safe reads and writes. This pattern is used throughout the JDK in utility methods like Collections.swap and Collections.fill.
At National Bank of India, Rajesh's utility library had a method that accepted List> of account objects and needed to shuffle them. The shuffle required set() operations, which the wildcard blocked. By introducing a private capture helper, the method could safely swap elements while keeping the public API flexible enough to accept any List regardless of element type.
Remember one sentence: List> is read-only because the compiler does not know the element type, and the capture helper pattern delegates to a private generic method where the wildcard is captured as a named type T for type-safe operations.
---