iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Generics

Generics let you write classes and methods that work over a type parameter while keeping compile-time safety. The type information is erased at runtime (no List reified), which has consequences — no `new T()`, no `instanceof T`, no T[] — but the day-to-day surface is the same as C# or TypeScript.

Generic methods, bounded types, and wildcards

EXAMPLE
import java.util.*;
import java.util.function.*;

public class GenericsDemo {

    // 1) Generic method
    public static <T> List<T> listOf(T... values) {
        var out = new ArrayList<T>(values.length);
        Collections.addAll(out, values);
        return out;
    }

    // 2) Bounded type parameter: T must implement Comparable<T>
    public static <T extends Comparable<T>> T max(List<T> xs) {
        if (xs.isEmpty()) throw new NoSuchElementException();
        T best = xs.get(0);
        for (int i = 1; i < xs.size(); i++) {
            if (xs.get(i).compareTo(best) > 0) best = xs.get(i);
        }
        return best;
    }

    // 3) Generic class with two type parameters
    public static class Pair<A, B> {
        final A first; final B second;
        public Pair(A a, B b) { this.first = a; this.second = b; }
        public <C> Pair<A, C> withSecond(C c) { return new Pair<>(first, c); }
        @Override public String toString() { return "(" + first + ", " + second + ")"; }
    }

    // 4) Wildcards
    //  ? extends T  -> covariant: producer of T
    //  ? super   T  -> contravariant: consumer of T
    public static double sumOf(List<? extends Number> xs) {
        double s = 0;
        for (Number n : xs) s += n.doubleValue();   // safe to READ as Number
        return s;
    }

    public static <T> void addAll(List<? super T> sink, Iterable<T> src) {
        for (T t : src) sink.add(t);                // safe to WRITE T into sink
    }

    // 5) Type-safe builder using a functional interface
    public static <T> List<T> build(Consumer<List<T>> body) {
        var list = new ArrayList<T>();
        body.accept(list);
        return list;
    }

    public static void main(String[] args) {
        var ints = listOf(1, 2, 3);
        System.out.println(max(ints));         // 3
        System.out.println(sumOf(ints));       // 6.0

        Pair<String, Integer> p1 = new Pair<>("life", 42);
        Pair<String, Double>  p2 = p1.withSecond(3.14);
        System.out.println(p2);                // (life, 3.14)

        List<Number> nums = new ArrayList<>();
        addAll(nums, ints);                    // OK: List<Number> super List<Integer>
        System.out.println(nums);

        List<String> built = build(l -> { l.add("a"); l.add("b"); });
        System.out.println(built);
    }
}

Why it matters

The PECS mnemonic — Producer Extends, Consumer Super — is the rule of thumb for wildcards. A method that reads from a collection takes `? extends T`; a method that writes into a collection takes `? super T`. Get this right and your APIs accept the most useful types without sacrificing safety.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
class Box<T> {
    T item;
    public void set(T item) { this.item = item; }
    public T get() { return item; }
}
Box<Integer> b = new Box<>();
b.set(42);
Try it Yourself »

Discussion

Loading…