Quiz 2

Generics — Type Parameters, Wildcards, and Type Erasure

1171 words
6 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# Generics — Type Parameters, Wildcards, and Type Erasure ## 🎯 Learning Objectives - Define generic classes, interfaces, and methods - Use bounded type parameters (`extends`, `super`) - Understand wildcards (`?`, `? extends T`, `?

Generics — Type Parameters, Wildcards, and Type Erasure

🎯 Learning Objectives

  • Define generic classes, interfaces, and methods
  • Use bounded type parameters (extends, super)
  • Understand wildcards (?, ? extends T, ? super T)
  • Explain type erasure and its implications
  • Compare generics with Python's dynamic typing

1. Why Generics?

1.1 The Problem (Before Generics)

java
List list = new ArrayList();
list.add("Hello");
list.add(42);  // Accidentally added Integer — no compile error
String s = (String) list.get(0);  // OK
String t = (String) list.get(1);  // ClassCastException at runtime!
Without generics, collections hold Object, and you must cast — the compiler can't check types.

1.2 The Solution (With Generics)

java
List<String> list = new ArrayList<>();
list.add("Hello");
list.add(42);  // Compile error! Can't add Integer to List<String>
String s = list.get(0);  // No cast needed — compiler knows it's String
Generics provide type safety at compile time, eliminating the need for casts.

2. Generic Classes

java
public class Box<T> {  // T is a type parameter
    private T content;
    public void set(T content) { this.content = content; }
    public T get() { return content; }
}
// Usage:
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get();  // No cast
Box<Integer> intBox = new Box<>();
intBox.set(42);
int i = intBox.get();
Naming conventions:
  • E — Element (collections)
  • K, V — Key, Value (maps)
  • T — Type
  • R — Return type

3. Generic Methods

java
public class Utils {
    // Generic method — type parameter before return type
    public static <T> T getMiddle(T... array) {
        return array[array.length / 2];
    }
}
// Type inference:
String mid = Utils.getMiddle("A", "B", "C");  // String
Integer mid2 = Utils.getMiddle(1, 2, 3, 4);   // Integer

4. Bounded Type Parameters

Restrict a type parameter to a specific hierarchy:
java
// T must be a Number or subclass
public static <T extends Number> double sum(T a, T b) {
    return a.doubleValue() + b.doubleValue();
}
sum(5, 10);       // OK (Integer)
sum(3.14, 2.71);  // OK (Double)
// sum("Hello", "World");  // Compile error!
Multiple bounds:
java
public class Bound<T extends Comparable<T> & Serializable> {
    // T must implement BOTH Comparable AND Serializable
}

5. Wildcards (?)

5.1 Unbounded Wildcard ?

java
public void printList(List<?> list) {  // Accept any type
    for (Object o : list) {
        System.out.println(o);
    }
    // list.add("X");  // ERROR! Can't add (type unknown)
}

5.2 Upper-Bounded Wildcard ? extends T

Read access allowed. Write not allowed (except null).
java
public double sumOfNumbers(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) {
        sum += n.doubleValue();
    }
    return sum;
}
sumOfNumbers(Arrays.asList(1, 2, 3));        // List<Integer>
sumOfNumbers(Arrays.asList(1.5, 2.5, 3.5));  // List<Double>

5.3 Lower-Bounded Wildcard ? super T

Write access allowed. Read gives Object.
java
public void addNumbers(List<? super Integer> list) {
    list.add(1);    // OK
    list.add(2);    // OK
    // Integer i = list.get(0);  // ERROR! Type is ? super Integer
    Object o = list.get(0);  // OK
}

5.4 PECS Rule (Producer Extends, Consumer Super)

  • ? extends Tproducer: you read T from it
  • ? super Tconsumer: you write T to it
java
// Copy from src (producer) to dest (consumer)
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
    for (T item : src) dest.add(item);
}

6. Type Erasure

At runtime, generic type information is erased. The compiler uses it for checking, then removes it.
java
// Source code:
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
// After erasure (at runtime):
List strings = new ArrayList();  // Both look identical
List integers = new ArrayList();
System.out.println(strings.getClass() == integers.getClass());  // true
Implications of erasure:
  1. Cannot use new T() (no new with type parameter)
  2. Cannot use instanceof T (no runtime type check)
  3. Cannot create generic arrays: new T[10]
  4. Static fields are shared across all parameterizations
java
public class MyClass<T> {
    // static T field;  // ERROR! T is erased
    // T[] array = new T[10];  // ERROR! Can't create generic array
    // if (obj instanceof T) { }  // ERROR! T erased
}

7. Java vs Python: Generics

FeatureJavaPython (3.5+ typing)
RuntimeErased (compile-time only)Present at runtime
EnforcementEnforced at compile timeType hints only (not enforced)
Syntax``Generic[T]
BoundsT extends NumberN/A
Wildcards? extends T, ? super TN/A
PerformanceNo overhead (erased)Some overhead
VarianceDeclaration-site + use-siteN/A

8. Practice Questions

Q1: Why can't you create new T() in a generic method?
Answer: Due to type erasure, T is erased to Object at runtime. new T() would become new Object(), which likely isn't what you want. The compiler prevents this. Q2: What is the difference between List and List?
Answer: List<Object> can only hold Object explicitly. List<?> accepts any parameterized List (List<String>, List<Integer>, etc.). You cannot add elements (except null) to List<?> because the type is unknown. Q3: What is the output?
java
List<Integer> ints = new ArrayList<>();
List<? extends Number> nums = ints;  // OK: Integer extends Number
Integer i = 42;
// nums.add(i);  // ERROR! Can't add to ? extends
Answer: The code compiles but the add is commented out. ? extends is a producer — you can read from it but not write to it (except null). Q4: What does PECS stand for?
Answer: Producer Extends, Consumer Super. Use ? extends T when you produce/read values. Use ? super T when you consume/write values. Q5: Can you pass List to a method expecting List?
Answer: No! This is a common misconception. List<Integer> is NOT a subtype of List<Object>. Generics are invariant by default. You need List<? extends Object> (which is just List<?>). Q6: Write a generic max method.
java
public static <T extends Comparable<T>> T max(T a, T b) {
    return (a.compareTo(b) >= 0) ? a : b;
}
Q7: Why do we need wildcards when we have bounded type parameters?
Answer: Bounded type parameters work at the method level (<T extends Number>). Wildcards work at the type usage level (List<? extends Number>). They enable variance in generic types, which is crucial for collections. Q8: What is the bridge method in the context of generics?
Answer: When a subclass overrides a generic method, the compiler sometimes generates a bridge method with erased types, to maintain polymorphism at the bytecode level. For example, if class MyComparator implements Comparator<String>, the compiler generates compare(Object, Object) that delegates to compare(String, String).

📐 Key Concepts

FeatureSyntaxPurpose
Generic classclass BoxType-safe container
Generic method T method(T arg)Type-safe method
Bounded type``Restrict type hierarchy
Unbounded wildcardListAccept any type
Upper boundedListRead T values
Lower boundedListWrite T values

🔗 Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.