Monday, September 1, 2025

Generics

 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 be List<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:

List<? extends Animal> animals = new ArrayList<Dog>(); animals.add(new Dog()); // ❌ Compilation error, cannot add Animal a1 = animals.get(0); // ✅ Safe

Important points:

  • animals could 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 Dog or Cat, only that it is at least an Animal.

So yes:

  • You cannot get the exact type (Dog or Cat) without a cast, similar to lower bounds.

  • ? extends → read-safe, write-restricted.

    ---------------------- List<? extends Animal> | |--- Read as Animal ✅ |--- Cannot add ❌
  • Upper Bound – ? extends Animal

  • Example: List<? extends Animal> animals = List<Dog>() or List<Cat>()

  • ✅ Safe read:

    Animal a = animals.get(0); // Always safe a.sound(); // Works
  • ❌ Unsafe write:

    animals.add(new Dog()); // Compiler error animals.add(new Cat()); // Compiler error
  • Reason: The exact subtype is unknown (Dog or Cat), so adding could break type safety.



import java.util.ArrayList; import java.util.List; public class Main { public static void showAnimals(List<? extends Animal> animals) { for (Animal a : animals) { // ✅ Reading as Animal is always safe a.sound(); } // animals.add(new Dog()); // ❌ Compilation error, cannot add // animals.add(new Cat()); // ❌ Compilation error, cannot add } public static void main(String[] args) { List<Dog> dogs = new ArrayList<>(); dogs.add(new Dog()); List<Cat> cats = new ArrayList<>(); cats.add(new Cat()); showAnimals(dogs); // ✅ Safe: Dog is an Animal showAnimals(cats); // ✅ Safe: Cat is an Animal } }

Why reading is safe

  • List<? extends Animal> could be a List<Dog> or List<Cat> — we don’t know exactly.

  • But we know every element is at least an Animal, because Dog and Cat are subclasses.

  • Therefore, iterating as Animal a is guaranteed safe, no casting needed.

Animal a1 = dogs.get(0); // ✅ Always safe Animal a2 = cats.get(0); // ✅ Always safe
  • We cannot assume it’s exactly a Dog or Cat, 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 be List<Dog>, List<Animal>, or List<Object>.

✅ Rules:

  • Write-safe: You can add T and any subclass of T.

  • Read only as Object: Reading elements gives you Object unless 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>() or List<Object>()

  • ✅ Safe write:

    animals.add(new Dog()); // Allowed animals.add(new Dog()); // Allowed // animals.add(new Cat()); // Compiler error
  • ❌ Restricted read:

    Object obj = animals.get(0); // Only Object is guaranteed Dog d = (Dog) animals.get(0); // Cast needed
  • Reason: The list could be List<Dog>, List<Animal>, or List<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 Dog and 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 ? extends when list is producing values to read).

    • Consumer Super (use ? super when 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?

CompletableFuture

  Welcome back to  our concurrency series ! In our first discussion, we likely touched on the traditional models of threading. Today, we’re ...