Generics
Generics let you write classes and methods that work with any type while still catching type
mistakes at compile time instead of at runtime. You've already been using them without a formal
introduction — every List<String> or Map<String, Integer> in the
Collections lesson was a generic type in action.
Why Generics Exist
Before generics, collections stored plain Object references, which meant you could accidentally
add the wrong type of item and only find out when your code crashed at runtime with a
ClassCastException. A raw, untyped List gives the compiler no information about what's
supposed to be inside it.
List names = new ArrayList(); // raw type - no type safety
names.add("Sonu");
names.add(42); // compiles fine, blows up later
With a generic type parameter, the compiler enforces the rule for you, right at the point where the mistake is made — no waiting for a crash in production.
List<String> names = new ArrayList<>(); // diamond operator
names.add("Sonu");
names.add(42); // compile error - int cannot be converted to String
<> on the right-hand side is called the diamond operator. The compiler infers the type from the declared variable, so you don't have to repeat String twice.Writing Your Own Generic Class
You aren't limited to using generics on built-in collections — you can write your own generic classes too.
The type parameter, conventionally a single capital letter like T (for "Type"), acts as a
placeholder that gets filled in with a real type when the class is used.
Example: A Generic Box
class Box<T> {
private T contents;
public void set(T contents) {
this.contents = contents;
}
public T get() {
return this.contents;
}
}
public class BoxDemo {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
Box<Integer> intBox = new Box<>();
intBox.set(99);
System.out.println(stringBox.get()); // Hello
System.out.println(intBox.get()); // 99
}
}
What's happening:
Box<T>is one class definition, but it works for any type you plug intoT.Box<String>andBox<Integer>are both backed by the sameBoxclass — no duplicate code needed for each type.- The compiler knows exactly what type
get()returns for each box, so no manual casting is required.
Bounded Type Parameters
Sometimes you want to restrict a type parameter to a family of types instead of allowing absolutely anything.
A bounded type parameter does this with the extends keyword.
class NumberBox<T extends Number> {
private T value;
public double doubled(T value) {
return value.doubleValue() * 2;
}
}
Here T extends Number means only Number or one of its subclasses — Integer,
Double, Long, and so on — can be used as the type argument. This lets the method safely
call doubleValue(), a method guaranteed to exist because every bound type is a Number.