Generics
Type-safe containers and reusable algorithms
Introduction
In this lesson, you'll learn about generics in Java. Coming from JavaScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In JavaScript, you're familiar with type-safe containers and reusable algorithms.
Java has its own approach to type-safe containers and reusable algorithms, which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
import java.util.*;
// Generic method
public static <T> T identity(T value) { return value; }
public static <T> T first(List<T> list) { return list.get(0); }
// Generic class
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
}
// Bounded wildcards
public static double sum(List<? extends Number> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
// Usage
List<Integer> nums = List.of(1, 2, 3);
List<String> strs = List.of("a", "b");
System.out.println(first(nums)); // 1
System.out.println(first(strs)); // "a"
Box<String> box = new Box<>("hello");Comparing to JavaScript
Here's how you might have written similar code in JavaScript:
// JS has no generics — types are dynamic
function identity(value) { return value; }
function first(arr) { return arr[0]; }
const nums = [1, 2, 3];
const strs = ["a", "b"];
console.log(first(nums)); // 1
console.log(first(strs)); // "a"
// No compile-time safety
const mixed = [1, "two", true];You may be used to different syntax or behavior.
Java generics are compile-time constructs; JS has no equivalent
You may be used to different syntax or behavior.
<T> declared on method/class; T can be bounded with 'extends'
You may be used to different syntax or behavior.
? extends T (covariant wildcard) — read-only; ? super T — write
You may be used to different syntax or behavior.
Type erasure: generics are erased at runtime (no List<Integer>.class)
You may be used to different syntax or behavior.
Collections API (List, Map, Set) are generic by design
Step-by-Step Breakdown
1. Generic Methods
Declare <T> before return type. Java infers T from the argument — just like dynamic dispatch in JS, but checked at compile time.
function wrap(val) { return { value: val }; }public static <T> Map<String,T> wrap(T val) {
return Map.of("value", val);
}2. Generic Classes
A generic class like Box<T> works for any type T. The type is fixed when you instantiate it.
class Box { constructor(v) { this.value = v; } }class Box<T> {
private T value;
Box(T v) { this.value = v; }
T get() { return value; }
}3. Bounded Type Parameters
Use 'extends' to restrict which types are valid. <T extends Comparable<T>> lets you call compareTo() on T.
function max(a, b) { return a > b ? a : b; }public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}4. Wildcards
Wildcards allow flexibility when the exact type doesn't matter — only the bounds do.
function printAll(list) { list.forEach(x => console.log(x)); }void printAll(List<?> list) {
for (Object item : list) System.out.println(item);
}Common Mistakes
When coming from JavaScript, developers often make these mistakes:
- Java generics are compile-time constructs; JS has no equivalent
- <T> declared on method/class; T can be bounded with 'extends'
- ? extends T (covariant wildcard) — read-only; ? super T — write
Key Takeaways
- Declare <T> before return type for generic methods; after class name for generic classes
- <T extends Foo> bounds the type; ? extends/super for wildcard flexibility
- Type erasure means no List<Integer>.class at runtime — use raw List.class
- Collections (List, Map, Set) are generic; always specify type parameters