Generics
Generics
Introduction
In this lesson, you'll learn about generics in Java. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with generics.
Java has its own approach to generics, which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
public static <T> T identity(T value) {
return value;
}
// Generic class
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public <U> Box<U> map(Function<T, U> fn) {
return new Box<>(fn.apply(value));
}
}
// Wildcard — upper bounded (like extends in TS)
public static double sum(List<? extends Number> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}Comparing to TypeScript
Here's how you might have written similar code in TypeScript:
function identity<T>(value: T): T {
return value;
}
interface Box<T> {
value: T;
map<U>(fn: (v: T) => U): Box<U>;
}
// Conditional type — TS-only feature
type Unwrap<T> = T extends Promise<infer U> ? U : T;You may be used to different syntax or behavior.
Java generics use type erasure — T becomes Object at runtime
You may be used to different syntax or behavior.
Wildcards <? extends T> and <? super T> replace TS conditional types for bounds
You may be used to different syntax or behavior.
No conditional types or mapped types in Java — these are TS-only
You may be used to different syntax or behavior.
<T extends Comparable<T>> is Java's equivalent of TypeScript's T extends SomeType
Step-by-Step Breakdown
1. Type Erasure
Java erases generic type parameters at runtime. List<String> and List<Integer> are both just List at runtime. You cannot do instanceof List<String>.
// TS keeps generic info through type system
function isStringArray(arr: unknown[]): arr is string[] {
return arr.every(x => typeof x === "string");
}// Java — cannot check generic type at runtime
List<String> list = new ArrayList<>();
// list instanceof List<String> — ILLEGAL2. Bounded Wildcards vs extends
Java wildcards ? extends T (covariant) and ? super T (contravariant) handle scenarios TypeScript handles with conditional types and covariance.
function process<T extends Animal>(items: T[]): void {}public static void process(List<? extends Animal> items) {}3. Function<T, R> as a Type
Java uses functional interfaces like Function<T,R>, Supplier<T>, Consumer<T> where TypeScript uses inline function types.
function transform<T, U>(fn: (v: T) => U, value: T): U {}public static <T, U> U transform(Function<T, U> fn, T value) {
return fn.apply(value);
}4. No Conditional or Mapped Types
TypeScript's advanced type features (conditional types, mapped types, infer) have no Java equivalent. Java generics are more limited by design.
type NonNullable<T> = T extends null | undefined ? never : T;// No equivalent in Java — handle at runtime with Objects.requireNonNull()
Objects.requireNonNull(value, "value must not be null");Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- Java generics use type erasure — T becomes Object at runtime
- Wildcards <? extends T> and <? super T> replace TS conditional types for bounds
- No conditional types or mapped types in Java — these are TS-only
Key Takeaways
- Java generics use type erasure — no runtime type info for generic parameters
- Wildcards <? extends T> and <? super T> handle variance
- Functional interfaces replace TS inline function types
- Advanced TS type-level features (conditional types, infer) have no Java equivalent