In Java Generics, wildcards (?
, ? extends
, ? super
) provide flexibility when designing APIs. Without wildcards, APIs can become rigid, requiring exact type matches. Wildcards make APIs more generalized and reusable while still preserving type safety.
Think of wildcards as universal adapters. Just like a travel adapter lets you plug different devices into one socket, wildcards allow methods to handle a broader range of generic inputs without breaking type safety.
This tutorial explores the role of wildcards in API design, explains extends vs super, introduces the PECS principle, and demonstrates real-world use cases.
Core Definition and Purpose of Java Generics
Generics were introduced in Java 5 to:
- Ensure type safety – Prevent runtime
ClassCastException
. - Promote reusability – Write one method/API usable across multiple types.
- Simplify APIs – Remove unnecessary casting.
Introduction to Type Parameters: <T>
, <E>
, <K, V>
<T>
– General type parameter.<E>
– Element type (common in collections).<K, V>
– Key-Value pair (used in maps).
Wildcards Explained: ?
, ? extends
, ? super
?
– Unknown type. Useful when type doesn’t matter.? extends T
– Upper bound (type is T or subtype).? super T
– Lower bound (type is T or supertype).
Example: Unbounded Wildcard
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
Upper Bounded Wildcards: ? extends T
When the API reads (produces) values.
public static void processNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n.doubleValue());
}
}
Usage:
processNumbers(Arrays.asList(1, 2, 3));
processNumbers(Arrays.asList(1.1, 2.2, 3.3));
Lower Bounded Wildcards: ? super T
When the API writes (consumes) values.
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
}
Usage:
List<Number> numbers = new ArrayList<>();
addNumbers(numbers); // Safe
PECS Principle: Producer Extends, Consumer Super
- Producer Extends (
? extends T
) → Use when reading. - Consumer Super (
? super T
) → Use when writing.
Example: Copy method.
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}
Wildcards vs Type Parameters
- Type Parameters (
<T>
) – Define reusable generic methods or classes. - Wildcards (
? extends
,? super
) – Add flexibility in method parameters.
Example:
// With type parameter
public static <T> void print(List<T> list) { ... }
// With wildcard
public static void print(List<?> list) { ... }
Wildcards in Collections Framework
List Example
List<? extends Number> nums = Arrays.asList(1, 2.5, 3);
Number n = nums.get(0); // Allowed
// nums.add(10); // Not allowed
Set Example
Set<? super Integer> set = new HashSet<>();
set.add(5);
Map Example
Map<? extends Number, ? super String> map = new HashMap<>();
Raw Types vs Parameterized Types with Wildcards
List list = new ArrayList(); // Raw type (unsafe)
List<?> safeList = new ArrayList<Integer>(); // Flexible and safe
Type Inference and Diamond Operator
Map<String, ? extends Number> map = new HashMap<>();
Type Erasure in Wildcards
At runtime, ?
, ? extends
, and ? super
are erased. Compile-time checks enforce constraints.
Reifiable vs Non-Reifiable Types
- Reifiable:
List<?>
(exists at runtime). - Non-Reifiable:
List<? extends Number>
(erased at runtime).
Recursive Type Bounds with Wildcards
public static <T extends Comparable<T>> T max(List<? extends T> list) {
return list.stream().max(Comparator.naturalOrder()).orElse(null);
}
Designing Fluent APIs with Wildcards
class FluentList<T> {
private List<T> list = new ArrayList<>();
public void addAll(List<? extends T> items) { list.addAll(items); }
public List<? super T> asSuperList() { return new ArrayList<>(list); }
}
Case Studies
Type-Safe Cache
class Cache<K, V> {
private Map<K, V> store = new HashMap<>();
public void put(K key, V value) { store.put(key, value); }
public V get(K key) { return store.get(key); }
}
Repository Pattern with Wildcards
interface Repository<T> {
void save(T entity);
List<? extends T> findAll();
}
Event Handling
interface EventListener<T> {
void onEvent(List<? super T> events);
}
Best Practices for Wildcards in APIs
- Use
extends
for producers,super
for consumers. - Prefer wildcards in API method parameters.
- Avoid raw types – use wildcards for flexibility.
- Keep method signatures readable.
Common Anti-Patterns
- Deeply nested wildcards (
List<? super List<? extends T>>
). - Overuse of
?
when<T>
is more appropriate. - Mixing raw types with wildcard APIs.
Performance Considerations
Wildcards introduce no runtime overhead. All checks happen at compile time, erased before runtime.
📌 What's New in Java for Generics?
- Java 5: Generics introduced (
extends
,super
). - Java 7: Diamond operator simplified instantiations.
- Java 8: Streams use wildcards extensively.
- Java 10:
var
integrates with wildcards. - Java 17+: Sealed classes enhance wildcard usage in hierarchies.
- Java 21: Virtual threads improve concurrency in generic collections.
Conclusion and Key Takeaways
Wildcards are a powerful tool for designing flexible APIs. They make method parameters adaptable without sacrificing type safety. By applying the PECS principle, developers can build APIs that are easy to use, reusable, and future-proof.
FAQ on Wildcards in APIs
Q1: Why use wildcards instead of type parameters?
Wildcards simplify API usage when exact type relationships aren’t needed.
Q2: Can I add elements to a List<? extends T>
?
No, only reading is safe.
Q3: Why does ? super T
return Object
when reading?
Because the type is unknown beyond being a supertype.
Q4: Can I mix wildcards and type parameters in one method?
Yes, this is common in flexible API design.
Q5: Do wildcards affect runtime performance?
No, they are erased at compile time.
Q6: What’s the difference between ?
and <T>
?<T>
defines a reusable type, ?
is for flexible method parameters.
Q7: Can I use wildcards in Maps?
Yes, e.g., Map<? extends Number, ? super String>
.
Q8: How do streams use wildcards?
Methods like map
, flatMap
use wildcards extensively.
Q9: When should I avoid wildcards?
When method design requires explicit type relationships.
Q10: What’s the best practice for API design with wildcards?
Use wildcards for flexibility in method parameters, type parameters for reusable class/method definitions.