Generics in Java
Learn how Java Generics provide type safety, reusable components, generic classes, methods, interfaces, wildcards, bounds, and type inference.
What are Generics?
Generics allow classes, interfaces, and methods to operate with parameterized types. They help catch type-related errors at compile time instead of relying on runtime casts.
Generics are widely used throughout the Java Collections Framework.
List<String> names =
new ArrayList<>();
names.add("Amit");
names.add("Neha");
Before Generics
Older Java code could store objects without specifying their intended type.
List values = new ArrayList();
values.add("Java");
values.add(100);
String text = (String) values.get(0);
Raw collections lose useful compile-time type checking and often require explicit casts.
Using Generics
List<String> values =
new ArrayList<>();
values.add("Java");
String text = values.get(0);
The compiler knows that the list contains Strings, so an explicit cast is not required.
Type Safety
List<String> names =
new ArrayList<>();
names.add("Java");
// Compile-time error:
// names.add(100);
Generics prevent incompatible values from being added to a parameterized collection.
Generic Classes
A generic class declares one or more type parameters.
class Box<T> {
private T value;
void set(T value) {
this.value = value;
}
T get() {
return value;
}
}
Using a Generic Class
Box<String> stringBox =
new Box<>();
stringBox.set("Hello");
String text = stringBox.get();
System.out.println(text);
Hello
Same Generic Class with Different Types
Box<Integer> numberBox =
new Box<>();
numberBox.set(100);
Box<Double> decimalBox =
new Box<>();
decimalBox.set(10.5);
The same generic class can be reused with different reference types.
Multiple Type Parameters
A generic class can declare multiple type parameters.
class Pair<K, V> {
private K key;
private V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
K getKey() {
return key;
}
V getValue() {
return value;
}
}
Using Multiple Type Parameters
Pair<Integer, String> student =
new Pair<>(101, "Amit");
System.out.println(
student.getKey()
);
System.out.println(
student.getValue()
);
Generic Methods
A method can declare its own type parameter independently of the class.
class Utility {
static <T> void print(T value) {
System.out.println(value);
}
}
Calling a Generic Method
Utility.print("Java");
Utility.print(100);
Utility.print(10.5);
Java can infer the appropriate type argument from the method invocation.
Generic Interfaces
interface Repository<T> {
void save(T value);
T find();
}
class StringRepository
implements Repository<String> {
private String value;
public void save(String value) {
this.value = value;
}
public String find() {
return value;
}
}
Bounded Type Parameters
A type parameter can be restricted using an upper bound.
class Calculator {
static <T extends Number>
double doubleValue(T value) {
return value.doubleValue();
}
}
Here, T must be a subtype of
Number.
Multiple Bounds
A type parameter can have multiple bounds. A class, when present, must come first, followed by interfaces.
<T extends Number & Comparable<T>>
Wildcards
The wildcard ? represents an unknown type.
List<?> values;
A wildcard is useful when code needs to work with a collection whose exact element type is not known.
Unbounded Wildcard
static void printList(
List<?> values
) {
for (Object value : values) {
System.out.println(value);
}
}
Upper-Bounded Wildcard
An upper-bounded wildcard uses ? extends Type.
List<? extends Number> numbers;
Such a reference can point to a list of Number or any subtype of Number.
Lower-Bounded Wildcard
A lower-bounded wildcard uses ? super Type.
List<? super Integer> values;
values.add(10);
values.add(20);
This is useful when a method needs to safely consume values of a particular type.
PECS Principle
-
Use
? extends Twhen a generic structure produces values of type T. -
Use
? super Twhen a generic structure consumes values of type T.
Diamond Operator
The diamond operator <> allows the compiler
to infer generic type arguments in many object creation
expressions.
List<String> names =
new ArrayList<>();
Raw Types
A raw type omits the generic type argument.
List values = new ArrayList();
Type Erasure
Java implements generics primarily through type erasure. Generic type information used for compile-time checking is generally erased from ordinary class and method signatures at runtime.
This provides compatibility with older Java bytecode while still giving developers compile-time type safety.
Generics and Primitive Types
Java type arguments must be reference types, not primitive types.
// Invalid:
// List<int> values;
// Correct:
List<Integer> values =
new ArrayList<>();
Wrapper classes such as Integer,
Double, and Boolean are used when
primitive values need to participate in generic APIs.
Generics and Autoboxing
Java can automatically convert between primitive values and their corresponding wrapper types in many contexts.
List<Integer> numbers =
new ArrayList<>();
numbers.add(10);
int value = numbers.get(0);
Practical Example: Generic Stack
class Stack<T> {
private List<T> items =
new ArrayList<>();
void push(T item) {
items.add(item);
}
T pop() {
return items.remove(
items.size() - 1
);
}
boolean isEmpty() {
return items.isEmpty();
}
}
Using the Generic Stack
Stack<String> stack =
new Stack<>();
stack.push("Java");
stack.push("Spring");
System.out.println(stack.pop());
Spring
Benefits of Generics
- Provides compile-time type safety.
- Reduces explicit casting.
- Makes reusable classes and methods possible.
- Improves readability of APIs.
- Works naturally with Java collections.
Generics Best Practices
- Prefer parameterized types over raw types.
-
Use meaningful type parameter names such as
T,K, andVwhere appropriate. - Use bounded wildcards when they express the intended API relationship.
- Follow the PECS principle for producer and consumer APIs.
- Avoid unnecessary unchecked casts and raw-type usage.
Interview Questions
? wildcard represents an unknown
generic type. It can be unbounded or constrained
using extends or
super.
Integer are used instead of
int.
Summary
Generics provide compile-time type safety and reusable parameterized components in Java. Important concepts include generic classes and methods, bounded type parameters, wildcards, PECS, type inference, raw types, and type erasure.