
Introduction To Java Generics
Learn the Introduction to Java Generics and understand how they enhance code reusability, type safety, and flexibility in Java programming.
1. Introduction
Java is a statically typed language, meaning that type checking happens at compile time. However, before Java 5, developers often used Object references to create collections or methods that could work with multiple data types. This approach was flexible but error-prone — type mismatches were caught only at runtime, not compile time.
To solve this problem, Java introduced Generics in Java 5, allowing developers to write code that is type-safe, reusable, and cleaner.
In simple words, Generics let you create classes, interfaces, and methods that can work with any type while still providing compile-time type safety.
2. What Are Generics in Java?
Generics mean “parameterized types.”
They allow you to create a class, interface, or method that can operate on different data types without rewriting the same code.
Example (Without Generics)
import java.util.*;
public class DemoWithoutGenerics {
public static void main(String[] args) {
List l = new ArrayList();
l.add("Ajay");
l.add(10); // No compile-time error
for (Object ob : l) {
String str = (String) ob; // ClassCastException at runtime
System.out.println(str);
}
}
}
Problem:
- No type checking at compile time
- Need to cast objects manually
- Runtime errors like ClassCastException
Example (With Generics)
import java.util.*;
public class DemoWithGenerics {
public static void main(String[] args) {
List<String> li = new ArrayList<>();
li.add("Ajay");
// li.add(10); // Compile-time error
for (String s : li) {
System.out.println(s); // No casting required
}
}
}
Benefits:
- Type safety ensured
- No explicit casting
- Cleaner and more readable code
3. Why Use Generics?
Generics in Java allow you to write flexible, reusable, and type-safe code. They enable classes, interfaces, and methods to operate on different data types without sacrificing compile-time type safety.
Advantages of Generics
- Compile-time type safety – Catch type errors early.
- Code reusability – Write once, use for multiple data types.
- Elimination of casting – Avoid Object-type conversions.
- Cleaner code – Less boilerplate and fewer runtime errors.
- Performance – Reduces overhead from unnecessary casting.
4. Generic Classes
Any class that can work with any form of data is called a generic class.
Syntax
class ClassName<T> {
private T dataValue; // T define for Type parameter
public ClassName(T dataValue) {
this. dataValue = dataValue;
}
public T getData() {
return dataValue;
}
}
Example
public class Box<T> {
private T valueData;
public void set(T valueData) {
this. valueData = valueData;
}
public T get() {
return valueData;
}
public static void main(String[] args) {
Box<Integer> boxInt= new Box<>();
boxInt.set(123);
System.out.println(boxInt.get());
Box<String> sBox = new Box<>();
sBox.set("Generics ");
System.out.println(strBox.get());
}
}
Here, T acts as a placeholder for the type.
When you create an object, T gets replaced by a specific type (like Integer or String).
5. Generic Methods
A generic method allows type parameters to be declared inside a method, independent of the class type.
Syntax
public <T> void display(T item) {
System.out.println(item);
}
Example
public class DemoGenericMethod{
public static <T> void printArray(T[] array) {
for (T ele : array) {
System.out.print(ele + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] iArr = {1, 2, 3, 4};
String[] sArr = {"A", "B", "C"};
printArray(iArr);
printArray(sArr);
}
}
Output:
1 2 3 4
A B C
6. Bounded Type Parameters
Sometimes, you want to restrict the types that can be used as arguments in a generic class or method.
Upper Bound
<T extends ClassName>
Means T can be any subtype of ClassName (or the class itself).
Example
class CalculatorDemo <T extends Number> {
public double square(T num) {
return num.doubleValue() * num.doubleValue();
}
}
public class BoundedDemo {
public static void main(String[] args) {
CalculatorDemo<Integer> intCalc = new CalculatorDemo <>();
System.out.println(intCalc.square(5));
CalculatorDemo <Double> doubleCalc = new CalculatorDemo <>();
System.out.println(doubleCalc.square(5.5));
}
}
Lower Bound
<? super Type>
Means the unknown type must be a superclass of the given type.
7. Wildcards in Generics
Wildcards (?) are used when you don’t know the exact type parameter.
Types of Wildcards
| Type | Syntax | Meaning |
|---|---|---|
| Unbounded | <?> | Any type |
| Upper bounded | <? extends T> | Type T or subtype of T |
| Lower bounded | <? super T> | Type T or supertype of T |
Example 1: Unbounded Wildcard
public static void printList(List<?> li) {
for (Object ob : li) {
System.out.println(ob);
}
}
Example 2: Upper-Bounded Wildcard
public static double sum(List<? extends Number> list) {
double total = 0.0;
for (Number num : list) {
total += num.doubleValue();
}
return total;
}
Example 3: Lower-Bounded Wildcard
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
}
Explore Other Demanding Courses
No courses available for the selected domain.
8. Multiple Type Parameters
You can use multiple type parameters separated by commas.
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; }
}
public class MultiTypeExample {
public static void main(String[] args) {
Pair<String, Integer> pair = new Pair<>("Age", 25);
System.out.println(pair.getKey() + ": " + pair.getValue());
}
}
9. Generic Interfaces
Interfaces can also be generic.
Example
interface Container<T> {
void add(T item);
T get();
}
class DataContainer<T> implements Container<T> {
private T data;
public void add(T item) { this.data = item; }
public T get() { return data; }
}
public class GenericInterfaceExample {
public static void main(String[] args) {
Container<String> c = new DataContainer<>();
c.add("Hello Generics");
System.out.println(c.get());
}
}
10. Type Inference (Diamond Operator)
From Java 7 onward, you can use the diamond operator (<>) to avoid repeating type parameters.
Example
List<String> list = new ArrayList<>(); // Type inferred
This reduces verbosity and improves readability.
11. Generic Constructors
Constructors can also be generic even if the class itself is not generic.
Example
class Demo {
public <T> Demo(T data) {
System.out.println("Value: " + data);
}
}
public class GenericConstructorExample {
public static void main(String[] args) {
Demo obj1 = new Demo(100);
Demo obj2 = new Demo("Java Generics");
}
}
12. Limitations of Generics
Although powerful, Generics have a few limitations:
- Cannot use primitive types (like int, char) — must use wrapper classes.
- List<int> list; // Invalid
- List<Integer> list; // Valid
- Type erasure: Type information is removed at runtime, meaning List<String> and List<Integer> are the same at runtime.
- Cannot create objects of type parameters
- T obj = new T(); // Not allowed
- Cannot use static fields with type parameters.
- Cannot use instanceof with parameterized types.
- if (obj instanceof List<String>) // Not allowed
13. Type Erasure (Behind the Scenes)
Java implements Generics using Type Erasure — meaning that generic type information is removed at compile time.
For example:
List<Integer> list = new ArrayList<>();
List<String> list2 = new ArrayList<>();
At runtime, both are treated as:
List list = new ArrayList();
This ensures backward compatibility with older Java code (pre-Java 5).
14. Real-Life Example
Let’s create a generic class that works for any type of data:
import java.util.*;
class Storage<T> {
private List<T> itemList = new ArrayList<>();
public void add(T itemData) {
itemList.add(itemData);
}
public void display() {
for (T item : itemList) {
System.out.println(item);
}
}
}
public class RealLifeGeneric {
public static void main(String[] args) {
Storage<String> names = new Storage<>();
names.add("Ajay");
names.add("Vijay");
names.display();
Storage<Integer> num = new Storage<>();
num.add(10);
num.add(20);
num.display();
}
}
This approach can easily be extended to store any data type — strings, integers, or even custom objects.
15. Conclusion
Generics are one of the most important and powerful features in Java.
They:
- Provide compile-time type safety
- Remove the need for casting
- Enable code reusability and cleaner design
By mastering generics, you make your code more flexible, robust, and maintainable — key qualities of professional Java development.
Do visit our channel to explore more: SevenMentor