This is one of the most confusing (but important) parts of generics and wildcards in Java. Let me explain this step by step — and once you understand the logic, it will make perfect sense. 🔥
🧩 The Core Problem: Type Safety
When you have an unbounded wildcard, like this:
List<?> list = new ArrayList<String>();
The wildcard ?
means:
“This list can hold elements of some type, but I don’t know which type exactly.”
🚨 Why Adding Elements is Dangerous
Since the compiler doesn’t know what type exactly ?
stands for, it cannot guarantee type safety if you try to add something.
For example:
List<?> list = new ArrayList<String>(); // Currently holds Strings
list.add("hello"); // Compile error: incompatible types
list.add(42); // Compile error: incompatible types
🧰 What type is ?
really?
- It could be a
List<String>
. - It could be a
List<Integer>
. - It could be a
List<User>
.
Since the compiler doesn’t know, it must block all “add” operations to prevent potential type mismatches.
💥 Example of the Danger (if adding was allowed)
Imagine this:
List<?> list = new ArrayList<String>();
list.add(42); // What if this was allowed?
String str = (String) list.get(0); // BOOM - ClassCastException!
The compiler has no way to check what type ?
is, so it cannot allow adding any object — the only safe value to add is null
(since null
is type-compatible with everything).
✅ What is Allowed
Operation | Allowed? | Reason |
---|---|---|
list.add(...) | ❌ | Unsafe — compiler doesn’t know the type. |
list.add(null) | ✅ | null fits any type. |
list.get(...) | ✅ | Safe — result is Object (superclass of all types). |
list.size() | ✅ | No type involved here. |
🔑 Summary Rule
❌ You cannot add elements to a
List<?>
because the type is unknown.✅ You can read elements (as
Object
), since every type extendsObject
.
⚙️ Mnemonic
✅ Safe for reading (because all types are Object
)
❌ Unsafe for writing (except null
, which is type-neutral)
📦 Example for Clarity
public static void processList(List<?> list) {
// list.add("hello"); // ❌ Doesn't compile
// list.add(new Object()); // ❌ Doesn't compile
list.add(null); // ✅ This is allowed
Object item = list.get(0); // ✅ Safe, we know it's at least an Object
System.out.println(item);
}
📣 Important: This applies only to ?
wildcards — if the type is known (like List<String>
), adding is perfectly fine.
✅ 1. ? extends T
— Upper Bounded Wildcard
List<? extends Number> list = new ArrayList<Integer>(); // Integer extends Number
🔗 What does ? extends Number
mean?
This list could be a
List<Number>
,List<Integer>
,List<Double>
, etc.
The exact type is unknown to the compiler — it only knows it’s some subtype of Number.
🚨 Can You Add Elements?
❌ NO, you cannot add anything (except null
).
list.add(42); // Compile error
list.add(3.14); // Compile error
list.add(new Number()); // Compile error
list.add(null); // ✅ This is allowed (null fits all types)
💣 Why Adding is Blocked?
The compiler doesn’t know the exact type of the list. It only knows it’s some subclass of Number
, but which one? Could be:
List<Integer>
List<Double>
List<Float>
If you try to add an Integer
, it might actually be a List<Double>
, which would break type safety.
✅ Can You Read Elements?
✅ YES, you can safely read — but as Number
, the upper bound.
Number n = list.get(0); // Safe
🔥 Summary for ? extends T
Operation | Allowed? | Why? |
---|---|---|
Add elements | ❌ | Unsafe, could break type safety. |
Add null | ✅ | Always safe. |
Read elements | ✅ | Safe as Number (the upper bound). |
✅ 2. ? super T
— Lower Bounded Wildcard
Example:
List<? super Integer> list = new ArrayList<Number>(); // Number is a supertype of Integer
🔗 What does ? super Integer
mean?
This list could be a
List<Integer>
,List<Number>
, or even aList<Object>
.
The compiler only knows the lower bound — the list can hold at leastInteger
or anything above it in the type hierarchy.
✅ Can You Add Elements?
✅ YES, you can add Integer
and its subtypes (if any).
list.add(42); // ✅ Integer fits
list.add(null); // ✅ null fits all
// list.add(new Object()); // ❌ Doesn't work
🚨 Can You Read Elements?
❌ NO (or very limited) — You can only read them as Object
.
Object obj = list.get(0); // Only safe option
// Integer i = list.get(0); // Compile error
Why? Because the actual list could be List<Object>
— so the compiler only guarantees that the items are at least Object
, not necessarily Integer
.
🔥 Summary for ? super T
Operation | Allowed? | Why? |
---|---|---|
Add T or subtype | ✅ | Always safe. |
Add null | ✅ | Always safe. |
Read elements | ✅ But only as Object | Actual type might be a supertype (like Object ). |
🚀 Final Recap Table
Wildcard | Add? | Read? |
---|---|---|
? (unbounded) | ❌ (except null ) | ✅ as Object |
? extends T | ❌ (except null ) | ✅ as T (upper bound) |
? super T | ✅ (T and subtypes) | ✅ as Object |
💡 Mnemonic
Wildcard | Meaning | Rule |
---|---|---|
? extends T | Producer | You can read safely (it produces), but can’t add. |
? super T | Consumer | You can add safely (it consumes), but reading is limited. |
🔗 Quick Analogy
Think of it like a vending machine vs. a donation box:
Wildcard | Analogy |
---|---|
? extends T | Vending machine — you can take out items, but you can’t put your own inside because you don’t know exactly what the machine accepts. |
? super T | Donation box — you can put items inside (it will accept anything “compatible”), but when you take something out, you might not know exactly what you get (so you handle it as Object ). |