Generics — Type Parameters & Wildcards

জেনেরিকস — Type Parameter ও Wildcard

Read: ~35 min Advanced 5 practice problems Live code runner

1. Why Generics Exist

Before Java 5, a List held plain Objects. You could put a String in and later, by mistake, an Integer — the compiler would not stop you, and your code would blow up at runtime with a ClassCastException. Generics fixed this by letting you parameterise a class or method by a type, so the compiler enforces consistency and you can drop the casts.

Java 5-এর আগে List-এ শুধু Object রাখা যেত — String-এর পাশে ভুল করে Integer রাখলে compile-এ ধরা পড়ত না, runtime-এ ClassCastException। Generics class/method-কে type দিয়ে parameterise করতে দেয়, তাই compiler-ই mismatch ধরে ফেলে এবং cast-এর প্রয়োজন থাকে না।
Main.java
import java.util.*;

class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Rahim");
        // names.add(42);          // ❌ compile error — compiler protects us
        String first = names.get(0);  // no cast needed
        System.out.println(first);
    }
}

2. A Generic Class of Your Own

Main.java
class Box<T> {
    private T value;
    Box(T value) { this.value = value; }
    public T get()       { return value; }
    public void set(T v)  { this.value = v; }
}

class Main {
    public static void main(String[] args) {
        Box<String>  s = new Box<>("hello");
        Box<Integer> n = new Box<>(42);
        System.out.println(s.get().length());  // String method, safe
        System.out.println(n.get() + 1);          // Integer auto-unbox
    }
}
<T> একটি type parameter — class বানানোর সময় একটি placeholder, instantiation-এ (Box<String>) কংক্রিট type বসে। Convention: T, E (element), K/V (key/value), R (result)।

3. Bounded Type Parameters

Sometimes you want to restrict a type parameter. <T extends Number> means "any T that is a subtype of Number" — inside the method you can safely call Number's methods on it.

Main.java
import java.util.*;

class Main {
    static <T extends Number> double sum(List<T> xs) {
        double total = 0;
        for (T x : xs) total += x.doubleValue();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.5, 2.5)));
    }
}

4. Wildcards and the PECS Rule

Generics in Java are invariant: a List<Integer> is NOT a List<Number>, even though Integer is a Number. Wildcards restore the flexibility you expect while preserving safety:

  • ? extends T — "some unknown subtype of T"; safe to read as T, unsafe to write. Producer.
  • ? super T — "some unknown supertype of T"; safe to write a T, unsafe to read specifically. Consumer.
PECS — Producer Extends, Consumer Super. If your parameter produces T (you read from it) use ? extends T. If it consumes T (you write to it) use ? super T.
PECS — Producer হলে extends, Consumer হলে super। থেকে পড়লে extends, তাতে লিখলে super।
Main.java
import java.util.*;

class Main {
    // src PRODUCES Numbers (we read) → ? extends Number
    // dst CONSUMES Numbers (we write)  → ? super Number
    static void copyNumbers(List<? extends Number> src,
                             List<? super Number> dst) {
        for (Number n : src) dst.add(n);
    }

    public static void main(String[] args) {
        List<Integer> ints   = List.of(1, 2, 3);
        List<Object>  target = new ArrayList<>();
        copyNumbers(ints, target);
        System.out.println(target);
    }
}

5. Type Erasure — The Honest Reality

Java generics are a compile-time feature. At runtime, all parametric type information is erased: a List<String> and a List<Integer> both become plain List. This kept Java's generics compatible with pre-Java-5 code, but it leads to a few quirks:

  • You cannot do new T[10] — the JVM does not know T at runtime.
  • instanceof List<String> is illegal — only instanceof List<?> is allowed.
  • Two overloads that differ only in their generic parameter are not allowed — they erase to the same signature.
Java generics compile-time feature — runtime-এ সব type parameter erase হয়ে যায়। এই erasure-ই Java-কে backward compatible রেখেছে, কিন্তু new T[], instanceof List<String>-এর মতো সীমাবদ্ধতাও এনেছে।

6. Vocabulary (শব্দভাণ্ডার)

TermMeaningবাংলায়
Type parameterPlaceholder like T filled in when instantiating.Placeholder — instantiation-এ কংক্রিট type বসে।
Generic classA class parameterised by one or more types.Type-parameterised class।
Bounded type<T extends Foo> — T must be Foo or a subtype.T অবশ্যই Foo বা তার subtype।
Wildcard? — unknown type.অজানা type।
Upper bounded ? extends TUnknown subtype of T — for reading.T-এর subtype — পড়ার জন্য।
Lower bounded ? super TUnknown supertype of T — for writing.T-এর supertype — লেখার জন্য।
PECSProducer Extends, Consumer Super.Producer হলে extends, Consumer হলে super।
Type erasureGeneric info stripped at runtime.Runtime-এ type তথ্য মুছে যায়।

7. Practice Problems

  1. Write a generic class Pair<A, B> with first() and second() accessors. Build a Pair<String, Integer> and print both values.
    Pair<A, B> generic class বানান (first(), second())। Pair<String, Integer> তৈরি করে দেখান।
    ✨ Show Answer
    Main.java
    class Pair<A, B> {
        private final A a;
        private final B b;
        Pair(A a, B b) { this.a = a; this.b = b; }
        A first()  { return a; }
        B second() { return b; }
    }
    class Main {
        public static void main(String[] args) {
            Pair<String, Integer> p = new Pair<>("Age", 28);
            System.out.println(p.first() + "=" + p.second());
        }
    }
  2. Explain in 2–3 sentences why List<Integer> is not a List<Number> even though Integer is a Number.
    দুই-তিন বাক্যে — Integer Number হলেও List<Integer> কেন List<Number> নয়?
    ✨ Show Answer

    Answer: If it were, you could pass a List<Integer> where a List<Number> is expected and then add(3.14) to it — silently breaking the original Integer list's invariants. Java therefore keeps generic types invariant, and offers wildcards (? extends Number) when you need flexibility in a read-only way.

    যদি এটা হতো, List<Integer>-এ 3.14 ঢুকে যেতে পারত — Integer list-এর invariant ভেঙে। তাই Java generics invariant, flexibility-র জন্য wildcard (? extends Number) দেওয়া হয়েছে।

  3. Write a generic method <T> T pickFirst(List<T> xs) that returns the first element — use it with both a List<String> and a List<Integer>.
    <T> T pickFirst(List<T> xs) generic method লিখুন — String ও Integer list-এ ব্যবহার করে দেখান।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        static <T> T pickFirst(List<T> xs) { return xs.get(0); }
        public static void main(String[] args) {
            System.out.println(pickFirst(List.of("Sumi", "Rahim")));
            System.out.println(pickFirst(List.of(10, 20, 30)));
        }
    }
  4. Which wildcard would you use for a parameter you only read from? Which for one you only write into?
    যে parameter থেকে শুধু পড়ছেন — কোন wildcard? যেটিতে শুধু লিখছেন — কোনটি?
    ✨ Show Answer

    Answer: Read-only (producer): ? extends T. Write-only (consumer): ? super T. This is the PECS rule — Producer Extends, Consumer Super.

    পড়লে ? extends T, লিখলে ? super T — PECS rule।

  5. Name one thing you can NOT do at runtime because of type erasure.
    Type erasure-এর কারণে runtime-এ যা যা করা যায় না তার একটি উল্লেখ করুন।
    ✨ Show Answer

    Answer: You cannot create a generic array (new T[10]) — the JVM does not know what T is at runtime, so it cannot pick the array's component type. The common workaround is to create an Object[] and cast, or use List<T> instead.

    Generic array new T[10] runtime-এ বানানো যায় না — JVM জানে না T কী। বিকল্প: Object[] বানিয়ে cast, বা List<T> ব্যবহার।

Summary — Module 21

Generics let you write one class or method that works safely with many types — the compiler enforces type correctness so you never need a cast. Type parameters can be bounded (T extends Number) so you can call methods on them inside. Wildcards restore flexibility across subtype relationships: ? extends T for reading (producers) and ? super T for writing (consumers) — the PECS rule. All of this is compile-time machinery: at runtime, Java erases type parameters, which is why a few operations (new T[], instanceof List<String>) are forbidden.

Generics দিয়ে একটি class/method বিভিন্ন type-এ নিরাপদে কাজ করে — compiler type মিলিয়ে দেয়, cast দরকার নেই। Type parameter-কে bounded করা যায় (T extends Number)। Wildcard: পড়লে ? extends T, লিখলে ? super T — PECS। সবই compile-time; runtime-এ type erase হয়ে যায় — তাই কিছু সীমাবদ্ধতা আছে।

Next Module → Arrays ও java.util.Arrays utility।