Generics
Type parameters, bounded types, wildcards (PECS), and type erasure.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
You've seen angle brackets everywhere: List<String>, Map<String, Integer>.
That's generics - Java's way to write code that works with any type while
staying completely type-safe. It's how collections avoid casting and catch
mistakes at compile time.
The problem generics solve
Before generics, collections held raw Objects. You had to cast everything, and
mistakes blew up at runtime:
List list = new ArrayList(); // old, raw style
list.add("hello");
String s = (String) list.get(0); // manual cast, ugly
list.add(42); // oops - compiles, but...
String bad = (String) list.get(1); // 💥 ClassCastException at runtimeWith generics, you declare the type once and the compiler enforces it:
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // no cast needed
// list.add(42); // ❌ won't even compile - caught early!An unlabeled box (raw type) could contain anything - you must open and check
every time. A box labeled "CDs only" (List<CD>) guarantees its contents:
the label-checker (compiler) refuses anything else at the door, so you never open
it to find a surprise. Generics are that type label.
Writing your own generic class
Use a type parameter (conventionally T) as a placeholder for a real type:
class Box<T> {
private T value;
void put(T value) { this.value = value; }
T get() { return value; }
}Box<String> sBox = new Box<>();
sBox.put("hi");
String s = sBox.get(); // typed as String, no cast
Box<Integer> iBox = new Box<>();
iBox.put(42);One class, safely reusable for any type. Common conventional names: T (type),
E (element), K/V (key/value).
Generic methods
Methods can be generic too:
static <T> T firstOf(List<T> items) {
return items.get(0);
}
String name = firstOf(List.of("Ada", "Grace")); // T inferred as StringBounded types: constraining T
Sometimes T must be something specific. A bound with extends requires
T to be a subtype:
// T must be a Number (or subclass), so we can call doubleValue()
static <T extends Number> double sum(List<T> nums) {
double total = 0;
for (T n : nums) total += n.doubleValue();
return total;
}Wildcards: flexible parameters (PECS)
? is a wildcard - "some unknown type". The rule of thumb is PECS:
Producer extends, Consumer super.
// Reading FROM (producing): use ? extends
double total(List<? extends Number> nums) { ... } // accepts List<Integer>, List<Double>...
// Writing INTO (consuming): use ? super
void addNumbers(List<? super Integer> list) { // accepts List<Integer>, List<Number>...
list.add(1);
list.add(2);
}Remember PECS
Producer Extends, Consumer Super. If a collection produces
values you read out, use ? extends. If it consumes values you put in, use
? super. This maximizes flexibility while staying type-safe.
Type erasure
Generics are a compile-time feature. After compilation, the type information
is largely erased - at runtime, List<String> and List<Integer> are both
just List. This has consequences:
- You can't do
new T()ornew T[]directly. - You can't check
if (x instanceof List<String>).
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true - both just ArrayListWhy erasure?
Erasure kept generics backward-compatible when they arrived in Java 5 - old code without generics still runs. The trade-off is that generic type info isn't available at runtime. In everyday code you'll rarely bump into this, but it's good to understand why some things aren't allowed.
Quick check
What is the main benefit of generics over using raw Object collections?
Key takeaways
- Generics let you write type-safe, reusable code that works with any type.
- A type parameter (T, E, K, V) is a placeholder for a real type supplied at use.
- Bounded types (<T extends Number>) constrain what T can be, enabling method calls on it.
- Wildcards use PECS: Producer extends (? extends), Consumer super (? super).
- Generics are erased at runtime (type erasure), so List<String> and List<Integer> are both just List.
Next, we'll leave memory behind and work with the outside world: reading and writing files.