Java Generics
Run each complete program separately. Its required helper classes and imports are included in the same code block. Short syntax fragments explain individual operations.
Generics let a class or method express which types it works with before the program runs. A List<String>, for example, asks the compiler to check String use at the places where elements enter and leave the list. Bounds and wildcards describe more flexible relationships without abandoning those checks.
For example:
List<String> names = new ArrayList<>();Here, String specifies that the list should contain only String values.
Generics were introduced in Java 5 and are heavily used throughout the Java Collections Framework.
What Are Generics in Java?
Generics allow a type to be specified as a parameter.
Instead of writing code that works only with one specific type, we can write code that works with different types while still maintaining type safety.
Without Generics
List list = new ArrayList();
list.add("Java");
list.add(100);
String value = (String) list.get(1);This can cause a runtime ClassCastException because the second element is an Integer, not a String.
With Generics
List<String> list = new ArrayList<>();
list.add("Java");
list.add(100); // Compile-time errorThe compiler prevents an invalid value from being added.
Why Do We Need Generics?
Generics provide several important benefits.
1. Type Safety
The compiler can detect incorrect types.
2. No Explicit Casting
Generics often eliminate the need for manual type casting.
3. Code Reusability
The same generic class or method can work with different types.
4. Better Readability
The expected type is clearly visible in the code.
5. Compile-Time Error Detection
Many type-related problems are detected during compilation instead of at runtime.
Simple Example
import java.util.ArrayList;
import java.util.List;
public class HelloWorld {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("John");
names.add("David");
names.add("Robert");
for (String name : names) {
System.out.println(name);
}
}
}Output
John
David
RobertBecause the list is declared as:
List<String>only String values can be added.
Generic Type Syntax
A generic type is written using angle brackets:
ClassName<Type>Examples:
List<String>
List<Integer>
List<Double>
Map<String, Integer>
Set<String>The type inside < > is called a type argument.
Generic Type Parameter
When defining a generic class or method, we use a type parameter.
Example
class Box<T> {
}Here:
Tis a type parameter.
When creating an object:
Box<String>String becomes the actual type argument.
Common Generic Type Parameter Names
Java does not require a specific name, but certain conventions are commonly followed.
| Type Parameter | Common Meaning |
|---|---|
T | Type |
E | Element |
K | Key |
V | Value |
N | Number |
S | State |
R | Result |
For example:
Map<K, V>means a map containing a key type and a value type.
Generic Class
A class can be made generic by declaring a type parameter after the class name.
Syntax
class ClassName<T> {
}Example
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}Now the same class can store different types.
Using a Generic Class with String
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class HelloWorld {
public static void main(String[] args) {
Box<String> box = new Box<>();
box.setValue("Java");
String value = box.getValue();
System.out.println(value);
}
}Output
JavaUsing a Generic Class with Integer
The same Box class can be used with Integer.
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class HelloWorld {
public static void main(String[] args) {
Box<Integer> box = new Box<>();
box.setValue(100);
Integer value = box.getValue();
System.out.println(value);
}
}Output
100The class did not need to be rewritten.
Generic Class with Multiple Type Parameters
A generic class can have more than one type parameter.
Example
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}Usage:
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
public class HelloWorld {
public static void main(String[] args) {
Pair<Integer, String> pair =
new Pair<>(101, "Java");
System.out.println(pair.getKey());
System.out.println(pair.getValue());
}
}Output
101
JavaHere:
K → Integer
V → StringGeneric Interface
Interfaces can also be generic.
Example
interface Container<T> {
void add(T value);
T get();
}A class can implement it with a specific type:
class StringContainer implements Container<String> {
private String value;
@Override
public void add(String value) {
this.value = value;
}
@Override
public String get() {
return value;
}
}Usage:
interface Container<T> {
void add(T value);
T get();
}
class StringContainer implements Container<String> {
private String value;
@Override
public void add(String value) {
this.value = value;
}
@Override
public String get() {
return value;
}
}
public class HelloWorld {
public static void main(String[] args) {
StringContainer container =
new StringContainer();
container.add("Java");
System.out.println(container.get());
}
}Output
JavaGeneric Methods
A method can have its own type parameter.
Syntax
public <T> void method(T value) {
}Notice that <T> appears before the return type.
Example
class Utility {
public <T> void print(T value) {
System.out.println(value);
}
}Usage:
class Utility {
public <T> void print(T value) {
System.out.println(value);
}
}
public class HelloWorld {
public static void main(String[] args) {
Utility utility = new Utility();
utility.print("Java");
utility.print(100);
utility.print(25.5);
}
}Output
Java
100
25.5The same method works with different types.
Generic Static Method
A generic method can also be static.
Example
class Utility {
public static <T> void print(T value) {
System.out.println(value);
}
}Usage:
class Utility {
public static <T> void print(T value) {
System.out.println(value);
}
}
public class HelloWorld {
public static void main(String[] args) {
Utility.print("Java");
Utility.print(100);
}
}Output
Java
100The type parameter belongs to the method, not to the class.
Generic Method Returning a Value
A generic method can return a generic type.
class Utility {
public static <T> T getValue(T value) {
return value;
}
}Usage:
class Utility {
public static <T> T getValue(T value) {
return value;
}
}
public class HelloWorld {
public static void main(String[] args) {
String name = Utility.getValue("Java");
Integer number = Utility.getValue(100);
System.out.println(name);
System.out.println(number);
}
}Output
Java
100Generic Method with Multiple Type Parameters
A method can have multiple type parameters.
class Utility {
public static <K, V> void printPair(K key, V value) {
System.out.println("Key: " + key);
System.out.println("Value: " + value);
}
}Usage:
class Utility {
public static <K, V> void printPair(K key, V value) {
System.out.println("Key: " + key);
System.out.println("Value: " + value);
}
}
public class HelloWorld {
public static void main(String[] args) {
Utility.printPair(101, "Java");
}
}Output
Key: 101
Value: JavaGeneric Constructor
Constructors can also use generic types when they belong to a generic class.
Example
class Box<T> {
private T value;
Box(T value) {
this.value = value;
}
T getValue() {
return value;
}
}Usage:
Box<String> box = new Box<>("Java");
System.out.println(box.getValue());Output
JavaType Inference with the Diamond Operator
Java provides the diamond operator:
<>It allows the compiler to infer the generic type.
Instead of:
List<String> names =
new ArrayList<String>();we can write:
List<String> names =
new ArrayList<>();The compiler infers String.
Generics with Collections
Generics are widely used with the Collections Framework.
ArrayList
List<String> names = new ArrayList<>();Set
Set<Integer> numbers = new HashSet<>();Map
Map<String, Integer> marks = new HashMap<>();Queue
Queue<String> queue = new LinkedList<>();Generics make these collections type-safe.
Generic Map Example
import java.util.HashMap;
import java.util.Map;
public class HelloWorld {
public static void main(String[] args) {
Map<Integer, String> students = new HashMap<>();
students.put(101, "John");
students.put(102, "David");
String name = students.get(101);
System.out.println(name);
}
}Output
JohnHere:
K → Integer
V → StringBounded Type Parameters
Sometimes we don't want a generic type to accept every possible type.
For example, we may want a method to work only with numbers.
We can use a bounded type parameter.
Syntax
<T extends Number>Example
class Calculator {
public static <T extends Number> double square(T number) {
double value = number.doubleValue();
return value * value;
}
}Usage:
class Calculator {
public static <T extends Number> double square(T number) {
double value = number.doubleValue();
return value * value;
}
}
public class HelloWorld {
public static void main(String[] args) {
System.out.println(
Calculator.square(10)
);
System.out.println(
Calculator.square(5.5)
);
}
}Output
100.0
30.25Here T must be Number or a subclass of Number.
Upper Bounded Type
The syntax:
<T extends SomeType>means:
T must be SomeType or a subclass/implementation compatible with the bound.
For classes:
<T extends Number>allows types such as:
Integer
Double
Float
Long
Short
Bytebecause these wrapper classes extend Number.
Multiple Bounds
A type parameter can have multiple bounds.
Example
<T extends Number & Comparable<T>>The class bound, if present, must appear first, followed by interface bounds.
Example
class Calculator {
public static <T extends Number & Comparable<T>>
T process(T value) {
return value;
}
}Multiple bounds are useful when a generic type needs to satisfy more than one requirement.
Wildcards
A wildcard is represented by:
?It means an unknown type.
Example
List<?> list;This means:
A list of some unknown type.
It could be:
List<String>
List<Integer>
List<Double>and so on.
Unbounded Wildcard
The simplest wildcard is:
?Example
public static void printList(List<?> list) {
for (Object value : list) {
System.out.println(value);
}
}Usage:
List<String> names =
List.of("John", "David");
List<Integer> numbers =
List.of(10, 20);
printList(names);
printList(numbers);Output
John
David
10
20The method can accept lists of different types.
Upper-Bounded Wildcard
An upper-bounded wildcard uses:
? extends TypeExample
List<? extends Number>This represents a list whose element type is Number or a subtype of Number.
It can accept:
List<Integer>
List<Double>
List<Float>Example
public static double sum(
List<? extends Number> numbers) {
double total = 0;
for (Number number : numbers) {
total += number.doubleValue();
}
return total;
}Usage:
List<Integer> numbers =
List.of(10, 20, 30);
System.out.println(sum(numbers));Output
60.0Lower-Bounded Wildcard
A lower-bounded wildcard uses:
? super TypeExample
List<? super Integer>This means the list can have a type of:
Integer
Number
ObjectExample
public static void addNumbers(
List<? super Integer> list) {
list.add(10);
list.add(20);
}Usage:
List<Number> numbers =
new ArrayList<>();
addNumbers(numbers);
System.out.println(numbers);Output
[10, 20]extends vs super
One of the most important concepts in Java generics is understanding:
? extends Tand:
? super T? extends T
Used when you mainly want to read values as T.
Example
List<? extends Number>? super T
Used when you need to add T values.
Example
List<? super Integer>A commonly used guideline is:
Producer Extends, Consumer Super (PECS)
If a generic structure produces values for you to read, extends is often appropriate.
If a generic structure consumes values that you want to add, super is often appropriate.
Example of PECS
Suppose we have:
public static double sum(
List<? extends Number> numbers) {
double total = 0;
for (Number number : numbers) {
total += number.doubleValue();
}
return total;
}The list is a producer of Number values, so:
? extends Numberis appropriate.
For a method that adds integers:
public static void addValues(
List<? super Integer> numbers) {
numbers.add(10);
numbers.add(20);
}The list is consuming Integer values, so:
? super Integeris appropriate.
Generic Class vs Wildcard
These two concepts are related but different.
Generic Class
class Box<T> {
T value;
}T represents a specific type parameter.
Wildcard
Box<?> box;? represents an unknown type.
Generic Array
Creating arrays of a type parameter directly is not allowed in the normal form:
T[] array = new T[10];This results in a compilation problem because Java does not allow direct creation of generic arrays like this.
Instead, common alternatives include:
Object[] array = new Object[10];or using collections such as:
List<T>For generic programming, collections are often more convenient than generic arrays.
Generic Exception Classes
Java does not allow a generic class to directly extend Throwable.
For example, this is not allowed:
class MyException<T> extends Exception {
}This restriction exists because Java's exception handling and runtime type system do not support generic throwable types in this form.
Primitive Types and Generics
Generics work with reference types, not primitive types.
This is invalid:
List<int> numbers;Instead, use the wrapper class:
List<Integer> numbers;Similarly:
int → Integer
double → Double
float → Float
long → Long
boolean → Boolean
char → Character
byte → Byte
short → ShortJava's autoboxing and unboxing make this convenient.
Example
List<Integer> numbers = new ArrayList<>();
numbers.add(10);The primitive 10 is automatically boxed into an Integer.
Type Erasure
One of the most important internal concepts of Java generics is type erasure.
Java generics are primarily a compile-time type-safety feature.
Consider:
List<String> names =
new ArrayList<>();The compiler uses the generic information to enforce type safety.
At runtime, generic type arguments are generally erased.
Conceptually, the runtime representation is closer to:
Listrather than retaining String as the collection's runtime element type.
The compiler inserts appropriate casts where necessary.
Why Does Java Use Type Erasure?
Type erasure allowed Java to introduce generics while maintaining compatibility with much of the existing Java ecosystem and bytecode model.
For example:
List<String>and:
List<Integer>are not distinct runtime classes.
Both are instances of:
ArrayListor another implementation of:
ListGeneric Type Information at Runtime
Consider:
List<String> names =
new ArrayList<>();You cannot normally do:
if (names instanceof List<String>) {
}This is not allowed because the generic type argument is not available in that form for runtime instanceof checking.
You can check:
if (names instanceof List<?>) {
}because List<?> represents a list of an unknown type.
Raw Types
A raw type is a generic type used without specifying its type parameter.
Example
List list = new ArrayList();This is a raw List.
Although it is allowed for compatibility with older Java code, it should generally be avoided in new code.
Prefer:
List<String> list =
new ArrayList<>();Raw types remove much of the compile-time type safety provided by generics.
Raw Type Example
List list = new ArrayList();
list.add("Java");
list.add(100);
String value = (String) list.get(1);This can result in:
ClassCastExceptionbecause the retrieved object is an Integer.
With generics:
List<String> list =
new ArrayList<>();
list.add("Java");the compiler prevents:
list.add(100);Generic Inheritance
Generic classes can participate in inheritance.
Example
class Box<T> {
T value;
}
class StringBox extends Box<String> {
}Here:
Box<T>
↓
Box<String>
↓
StringBoxThe subclass fixes the generic type to String.
Generic Interface Implementation
Example
interface Repository<T> {
void save(T value);
}Implementation:
class UserRepository
implements Repository<String> {
@Override
public void save(String value) {
System.out.println(
"Saving: " + value
);
}
}Usage:
Repository<String> repository =
new UserRepository();
repository.save("John");Output
Saving: JohnGeneric Interface with Generic Implementation
Instead of fixing the type, the implementing class can remain generic.
interface Repository<T> {
void save(T value);
}
class DatabaseRepository<T>
implements Repository<T> {
@Override
public void save(T value) {
System.out.println(
"Saving: " + value
);
}
}Now:
Repository<String> stringRepository =
new DatabaseRepository<>();
Repository<Integer> integerRepository =
new DatabaseRepository<>();The same implementation works with different types.
Generic Methods vs Generic Classes
These are not the same.
Generic Class
class Box<T> {
T value;
}The type parameter belongs to the class.
Generic Method
class Utility {
public <T> void print(T value) {
System.out.println(value);
}
}The type parameter belongs only to the method.
A non-generic class can contain generic methods.
Complete Generic Utility Example
import java.util.List;
class Utility {
public static <T> void printList(
List<T> list) {
for (T value : list) {
System.out.println(value);
}
}
}
public class HelloWorld {
public static void main(String[] args) {
List<String> names =
List.of("John", "David");
List<Integer> numbers =
List.of(10, 20, 30);
Utility.printList(names);
System.out.println("---");
Utility.printList(numbers);
}
}Output
John
David
---
10
20
30Benefits of Generics
Type Safety
Incorrect types are detected during compilation.
Reusability
One implementation can work with many types.
Reduced Casting
Generic code often eliminates explicit casts.
Better Readability
The expected type is visible directly in the code.
Better API Design
Generic classes and methods allow developers to create flexible and type-safe APIs.
Limitations of Generics
1. Cannot Use Primitive Types Directly
Use:
Integerinstead of:
int2. Type Erasure
Generic type arguments are generally not available in the same way at runtime.
3. Cannot Directly Create Generic Arrays
new T[10]is not allowed.
4. Cannot Use Generic Type Arguments with Certain Runtime Operations
For example:
obj instanceof List<String>is not allowed.
5. Generic Type Parameters Cannot Be Used Everywhere
Java has specific restrictions around static members, arrays, exceptions, and runtime type checks.
Common Mistakes
Mistake 1: Using Raw Types
Avoid:
List list = new ArrayList();Prefer:
List<String> list =
new ArrayList<>();Mistake 2: Using Primitive Types
Incorrect:
List<int> numbers;Correct:
List<Integer> numbers;Mistake 3: Confusing T and ?
Consider:
List<T>and:
List<?>T represents a type parameter that is part of the generic declaration.
? represents an unknown type.
Mistake 4: Assuming extends Means Only Classes
In generic bounds:
<T extends SomeType>extends can also be used with interfaces.
For example:
<T extends Comparable<T>>Here Comparable<T> is an interface.
Mistake 5: Adding Values to List<?>
Consider:
List<?> list;You cannot generally add an arbitrary value:
list.add("Java");because the actual element type is unknown.
You can safely add:
list.add(null);because null is compatible with reference types.
Important Generic Syntax
Generic class
class Box<T> {
}Multiple type parameters
class Pair<K, V> {
}Generic method
<T> T method(T value) {
return value;
}Upper bound
<T extends Number>Unbounded wildcard
List<?>Upper-bounded wildcard
List<? extends Number>Lower-bounded wildcard
List<? super Integer>Quick Comparison
| Concept | Example | Meaning |
|---|---|---|
| Type parameter | T | Generic type declared by class/method |
| Type argument | String | Actual type supplied |
| Generic class | Box<T> | Class works with a type |
| Generic method | <T> T get() | Method works with a type |
| Wildcard | ? | Unknown type |
| Upper bound | ? extends Number | Number or subtype |
| Lower bound | ? super Integer | Integer or supertype |
| Raw type | List | Generic type without type argument |
| Diamond operator | <> | Compiler infers type arguments |
Generics Interview Questions
What are generics in Java?
Generics provide a way to write reusable and type-safe code by parameterizing types.
What is the benefit of generics?
The main benefits include type safety, code reuse, reduced casting, and better readability.
What is a type parameter?
A type parameter is a placeholder for a type.
Example
class Box<T>Here T is the type parameter.
What is a type argument?
The actual type supplied to a generic type.
Example
Box<String>Here String is the type argument.
What is a wildcard?
? represents an unknown type.
Example
List<?>What is an upper-bounded wildcard?
? extends NumberIt represents Number or one of its subtypes.
What is a lower-bounded wildcard?
? super IntegerIt represents Integer or one of its supertypes.
What is type erasure?
Type erasure is the mechanism by which Java removes most generic type-argument information from the runtime representation while preserving compile-time type checking.
Can we use primitive types with generics?
No.
Use wrapper classes instead:
List<Integer>rather than:
List<int>What is PECS?
PECS stands for:
Producer Extends, Consumer Super.
It is a guideline for choosing between extends and super when working with wildcards.
Key Points
- Generics provide compile-time type safety.
- Generics make code reusable.
- Generic types use type parameters such as
T,E,K, andV. - A class can be generic.
- An interface can be generic.
- A method can be generic.
- Multiple type parameters are supported.
- Generic types work extensively with Java collections.
- Primitive types cannot be used directly as generic type arguments.
- Wrapper classes are used instead.
?represents an unknown type.? extends Trepresents an upper-bounded wildcard.? super Trepresents a lower-bounded wildcard.- Bounded type parameters restrict the types that can be used.
- Raw types should generally be avoided.
- Java generics use type erasure.
- The diamond operator
<>allows type inference. - PECS means Producer Extends, Consumer Super.
Conclusion
Generics are one of the most important features of Java's type system. They allow developers to create code that is both reusable and type-safe.
The basic idea can be summarized as:
Generics
↓
Parameterize Types
↓
Compile-Time Type Safety
↓
Reusable Code
↓
Less CastingOnce the basic syntax is understood, the most important concepts to master are:
Generic Class
↓
Generic Method
↓
Bounded Type
↓
Wildcard
↓
? extends
↓
? super
↓
PECS
↓
Type ErasureGenerics are particularly important when working with the Java Collections Framework, APIs, libraries, and enterprise applications because they allow developers to define flexible APIs without sacrificing compile-time type safety.
See you in next page....🙂