Return to site

✨☕ JAVA GENERICS — PRACTICAL RULES YOU’LL ACTUALLY USE

· java
Section image

Your learning drop for cleaner APIs and safer code. Below are 7 field-tested habits (with tiny snippets) to make your generics rock.

TL;DR

  1. ▪️ Avoid raw types, kill unchecked warnings, and prefer List over arrays.
  2. ▪️ Make your types and methods generic to push errors to compile time.
  3. ▪️ Use bounded wildcards (? extends / ? super) to keep APIs flexible.
  4. ▪️ For advanced cases, typesafe heterogeneous containers give you compile-time safety without casts.

🔸 DON’T USE RAW TYPES IN NEW CODE

Always parameterize collections and generics—raw types drop type safety.

// ❌ Raw type — loses type info
List raw = new ArrayList();
raw.add(42);

// ✅ Parameterized
List<Integer> nums = new ArrayList<>();
nums.add(42); // type-checked 

Why: Raw types bypass generics checking → casts, ClassCastException at runtime.

🔸 ELIMINATE UNCHECKED WARNINGS

Make the compiler happy—warnings often hide real type risks.

// ✅ Narrow the warning with a helper (into a tiny, well-named method (the “helper”))
static <T> List<T> asListSafe(T... items) {
    return List.of(items); // no unchecked cast
}

// If you *must* suppress, do it locally and prove correctness:
@SuppressWarnings("unchecked")
static <T> T[] toArray(List<T> list, IntFunction<T[]> factory) {
    return list.toArray(factory.apply(list.size())); // safe boundary
}

Tip: Prefer designs that avoid @SuppressWarnings; if used, document why it’s safe.

🔸 PREFER LISTS TO ARRAYS

Choose List

over T[]—because arrays are reified & covariant (checks at runtime), while generics are erased & invariant (checks at compile time).

// ❌ Array-based API (covariant, runtime surprises)
void addAll(Number[] dst, Integer[] src) { /* ... */ }

// ✅ List-based API (invariant, compile-time checks)
void addAll(List<Number> dst, List<Integer> src) { /* ... */ }

Reason: Arrays are covariant + runtime checks; Lists are invariant + compile-time checks.

🔸 FAVOR GENERIC TYPES

Make your classes generic instead of storing Object.

// ❌ Not generic
final class Box {
    private Object value;
    Object get() { return value; }
    void set(Object v) { value = v; }
}

// ✅ Generic type
final class Box<T> {
    private T value;
    T get() { return value; }
    void set(T v) { value = v; }
}

Box<String> b = new Box<>();
b.set("hello"); // type-safe 

Outcome: Clearer APIs, no casting, fewer runtime errors.

🔸 FAVOR GENERIC METHODS

Let methods declare their own

to reduce casting and overload noise.

// ✅ Generic static factory
public static <T> Set<T> union(Set<T> a, Set<T> b) {
    Set<T> r = new HashSet<>(a);
    r.addAll(b);
    return r;
}

Set<String> s = union(Set.of("a"), Set.of("b"));

Bonus: Generic methods compose nicely in fluent APIs.

🔸 USE BOUNDED WILDCARDS FOR FLEXIBLE APIS

PECS: Producer Extends, Consumer Super.

// Reads/produces T => ? extends T
static <T> void copyAll(List<? extends T> src, List<? super T> dst) {
    for (T t : src) dst.add(t);
}

List<Integer> src = List.of(1, 2, 3);
List<Number>  dst = new ArrayList<>();
copyAll(src, dst); // ✅ flexible 

Rule:

  1. ▪️ If param produces values → ? extends T
  2. ▪️ If param consumes values → ? super T

🔸 CONSIDER TYPESAFE HETEROGENEOUS CONTAINERS

Key the values by type to store different types safely—no casts.

final class TypeSafeMap {
    private final Map<Class<?>, Object> map = new HashMap<>();

    public <T> void put(Class<T> type, T value) {
        map.put(type, type.cast(value));
    }
    public <T> T get(Class<T> type) {
        return type.cast(map.get(type));
    }
}

interface Handler<T> { void handle(T msg); }

var registry = new TypeSafeMap();
registry.put(UserCreated.class, (Handler<UserCreated>) msg -> {/*...*/});
registry.put(OrderPaid.class,   (Handler<OrderPaid>)   msg -> {/*...*/});

Handler<UserCreated> h = registry.get(UserCreated.class);

Use cases: Registries, context bags, DI-lite containers.

TAKEAWAYS

  1. ▪️ Generics push errors left (compile time > runtime).
  2. ▪️ Lists beat arrays for API design.
  3. ▪️ PECS makes wildcard choices straightforward.
  4. ▪️ Avoid @SuppressWarnings unless you can prove safety.
  5. ▪️ Advanced: typesafe heterogeneous containers keep flexibility and safety.

Go further with Java certification:

Java👇

Spring👇