Perfect 👍 Let’s put everything together into a complete, step-by-step summary of Java Generics with upper/lower bounds, based on our whole conversation.
I’ll stick to the Animal / Dog / Cat / Puppy example (instead of Car/Honda/Toyota) for consistency.
🐾 Java Generics — Upper Bound vs Lower Bound (with Animal Example)
1. Why Generics Exist
Java Generics provide type safety and flexibility.
Without them, you could insert the wrong type into a collection (e.g., adding a Cat to a List<Dog>), which breaks runtime safety.
Generics enforce these rules at compile time.
2. Upper Bound — <? extends T>
Definition:
-
"Some unknown subtype of
T" -
Example:
List<? extends Animal>→ could beList<Dog>,List<Cat>, etc.
✅ Rules:
-
Read-safe: You can read elements as type
T. -
Write-restricted: You cannot add new elements (except
null).
Example:
1️⃣ Upper bound (? extends Animal)
Example:
Important points:
animalscould be List<Dog>, List<Cat>, or List<Animal>.The compiler doesn’t know the exact subtype at compile time.
Therefore, when you read from it, you cannot assume it is a
DogorCat, only that it is at least anAnimal.
So yes:
You cannot get the exact type (Dog or Cat) without a cast, similar to lower bounds.
---------------------- List<? extends Animal> | |--- Read as Animal ✅ |--- Cannot add ❌? extends→ read-safe, write-restricted.Upper Bound –
? extends Animal
Example:
List<? extends Animal> animals = List<Dog>()orList<Cat>()✅ Safe read:
❌ Unsafe write:
Reason: The exact subtype is unknown (
DogorCat), so adding could break type safety.
Why reading is safe
-
List<? extends Animal>could be aList<Dog>orList<Cat>— we don’t know exactly. -
But we know every element is at least an
Animal, becauseDogandCatare subclasses. -
Therefore, iterating as
Animal ais guaranteed safe, no casting needed.
We cannot assume it’s exactly a
DogorCat, so the compiler prevents adding elements.
| Bound | Can Add? | Can Read? | Why |
|---|---|---|---|
? extends Animal |
❌ Cannot add | ✅ Safe as Animal |
All elements are guaranteed to be some subclass of Animal, so reading is safe. |
? super Dog |
✅ Can add Dog/subclass | ✅ Only Object (cast needed) | List could be List<Dog>, List<Animal>, or List<Object>, so exact type unknown. |
3. Lower Bound — <? super T>
Definition:
-
"Some unknown supertype of
T" -
Example:
List<? super Dog>→ could beList<Dog>,List<Animal>, orList<Object>.
✅ Rules:
-
Write-safe: You can add
Tand any subclass ofT. -
Read only as Object: Reading elements gives you
Objectunless you cast.
Example:
public static void addDogs(List<? super Dog> animals) {
animals.add(new Dog()); // ✅ Safe
animals.add(new Puppy()); // ✅ Safe (subclass of Dog)
// animals.add(new Animal()); // ❌ Not allowed (superclass)
// animals.add(new Cat()); // ❌ Not allowed (sibling)
Object obj = animals.get(0); // ✅ Safe, but only as Object
Animal a = (Animal) animals.get(0); // ✅ With cast
}
✔ You can insert Dog and its subclasses.
❌ You cannot insert superclasses (Animal, Object) or siblings (Cat).
⚠ Reads are limited to Object, since compiler doesn’t know the exact type.
Lower Bound (super) ---------------------- List<? super Dog> | |--- Can add Dog/subclass ✅ |--- Read as Object only (cast needed) ❌
4. Why These Rules Exist
Imagine there were no bounds rules:
List<Cat> cats = new ArrayList<>();
List<Animal> animals = (List<Animal>)(Object) cats; // ⚠ Unsafe cast
animals.add(new Dog()); // ❌ Now List<Cat> contains a Dog → type safety broken
This would cause runtime ClassCastException.
Generics prevent this at compile time.
Lower Bound – ? super Dog
-
Example:
List<? super Dog> animals = List<Animal>()orList<Object>() -
✅ Safe write:
-
❌ Restricted read:
-
Reason: The list could be
List<Dog>,List<Animal>, orList<Object>, so exact type is unknown.
5. Safe vs Unsafe (Summary Table)
| Wildcard | Meaning | Read | Write |
|---|---|---|---|
List<? extends T> |
Some subtype of T |
✅ Safe as T |
❌ Cannot add (except null) |
List<? super T> |
Some supertype of T |
⚠ Only Object (cast needed) |
✅ Safe: add T or subclass |
Plain List<T> |
Exactly T |
✅ As T |
✅ Add T |
Extra clarification for lower bound (? super Dog):
-
✅ You can add
Dogand subclasses (Puppy). -
❌ You cannot add
Animal,Object, or sibling (Cat).
6. Practical SOLID-Style Placement
We also discussed moving addDog() inside the Dog class (instead of utility method).
Example:
class Dog extends Animal {
public void addTo(List<? super Dog> animals) {
animals.add(this); // ✅ Dog adds itself
}
}
⚠ Hypothetically, if you let a Dog add itself into a List<Cat>, type safety breaks —
but generics prevent that at compile time.
7. Key Takeaways
-
Upper bound (
? extends T) → Good for reading, bad for writing. -
Lower bound (
? super T) → Good for writing, limited for reading. -
Bounds exist to prevent inserting the wrong type (e.g., Dog in a Cat list).
-
PECS rule helps remember:
-
Producer Extends (use
? extendswhen list is producing values to read). -
Consumer Super (use
? superwhen list is consuming values to write).
-
✅ Now you’ve seen both upper and lower bounds with:
-
Definitions
-
Examples (
Animal,Dog,Cat,Puppy) -
Why compiler restricts certain operations
-
A summary table
-
A SOLID-style placement example
Would you like me to also make a single runnable Java file containing all these examples (upper + lower + unsafe cast + table as comments) so you can just copy/paste and practice?
No comments:
Post a Comment