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 leastIntegeror 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). |