✅ Generics in Java allow you to write type-safe, reusable, and flexible code by enabling parameterized types. They help avoid type casting and runtime ClassCastException
errors.
1. Why Use Generics?
🚀 Before Generics (Java <5), You Had to Use Object
import java.util.*;
public class WithoutGenerics {
public static void main(String[] args) {
List list = new ArrayList(); // No type specified
list.add("Java");
list.add(10); // Allowed, but problematic
String s = (String) list.get(0); // ✅ Works
String num = (String) list.get(1); // ❌ ClassCastException at runtime
}
}
🛑 Problem: Since List
holds Object
, manual casting is needed, and incorrect casting leads to runtime errors.
🚀 With Generics (Java 5+)
import java.util.*;
public class WithGenerics {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); // Type-safe collection
list.add("Java");
// list.add(10); // ❌ Compilation error (not a String)
String s = list.get(0); // ✅ No casting needed, type safety ensured
}
}
✅ Advantages of Generics:
- Eliminates
ClassCastException
at runtime. - Ensures type safety at compile time.
- Code reusability with parameterized types.
2. How to Declare a Generic Class
A generic class defines a type parameter (T
), which is replaced with a specific type at runtime.
✅ Example: Creating a Generic Class
// Generic class with type parameter T
class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
public class GenericClassExample {
public static void main(String[] args) {
Box<String> strBox = new Box<>(); // Box of Strings
strBox.set("Hello Generics");
System.out.println(strBox.get()); // Output: Hello Generics
Box<Integer> intBox = new Box<>(); // Box of Integers
intBox.set(100);
System.out.println(intBox.get()); // Output: 100
}
}
🔹 Key Takeaway: T
acts as a placeholder that is replaced with String
, Integer
, or any other type.
3. Generics in Methods
A generic method allows type parameters only for that method.
✅ Example: Generic Method
class Utility {
public static <T> void print(T item) {
System.out.println(item);
}
}
public class GenericMethodExample {
public static void main(String[] args) {
Utility.print("Hello"); // String
Utility.print(123); // Integer
Utility.print(3.14); // Double
}
}
4. Generics in Interfaces
✅ Example: Generic Interface
interface Container<T> {
void set(T item);
T get();
}
// Implementing the generic interface
class StringContainer implements Container<String> {
private String item;
public void set(String item) { this.item = item; }
public String get() { return item; }
}
public class GenericInterfaceExample {
public static void main(String[] args) {
Container<String> strContainer = new StringContainer();
strContainer.set("Generic Interface");
System.out.println(strContainer.get()); // Output: Generic Interface
}
}
🔹 Key Takeaway: Generic interfaces allow flexibility in implementing classes.
5. Bounded Type Parameters
You can restrict generic types using bounded type parameters (extends
keyword).
✅ Example: Allow Only Number
Types
class Calculator<T extends Number> { // T must be a subclass of Number
private T num;
public Calculator(T num) {
this.num = num;
}
public double square() {
return num.doubleValue() * num.doubleValue();
}
}
public class BoundedGenericsExample {
public static void main(String[] args) {
Calculator<Integer> intCalc = new Calculator<>(5);
System.out.println(intCalc.square()); // Output: 25.0
Calculator<Double> doubleCalc = new Calculator<>(3.5);
System.out.println(doubleCalc.square()); // Output: 12.25
// Calculator<String> strCalc = new Calculator<>("Hello"); ❌ Compilation error
}
}
🔹 Key Takeaway: <T extends Number>
ensures T
is only a Number
type (Integer
, Double
, etc.).
6. Wildcards in Generics (?
)
Wildcards (?
) allow unknown types in generics.
✅ Example: Unbounded Wildcard (?
)
import java.util.*;
public class WildcardExample {
public static void printList(List<?> list) { // Accepts any type of List
for (Object item : list) {
System.out.println(item);
}
}
public static void main(String[] args) {
List<String> strList = Arrays.asList("A", "B", "C");
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(strList); // Works
printList(intList); // Works
}
}
🔹 Key Takeaway: List<?>
means any type of List
can be passed.
✅ Example: Upper Bounded Wildcard (? extends T
)
public class UpperBoundExample {
public static double sum(List<? extends Number> list) { // Only Number or subclass
double total = 0;
for (Number num : list) {
total += num.doubleValue();
}
return total;
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
System.out.println(sum(intList)); // Output: 6.0
System.out.println(sum(doubleList)); // Output: 7.5
}
}
🔹 Key Takeaway: ? extends Number
allows Integer
, Double
, etc.
✅ Example: Lower Bounded Wildcard (? super T
)
public class LowerBoundExample {
public static void addNumbers(List<? super Integer> list) { // Only Integer or superclass (Number, Object)
list.add(10);
}
public static void main(String[] args) {
List<Number> numList = new ArrayList<>();
addNumbers(numList);
System.out.println(numList); // Output: [10]
}
}
🔹 Key Takeaway: ? super Integer
allows Integer
and its superclasses (Number
, Object
).
7. Summary Table
Feature | Description | Example |
---|---|---|
Generic Class | Class with a type parameter | class Box<T> {} |
Generic Method | Method with a type parameter | <T> void print(T item) {} |
Generic Interface | Interface with a type parameter | interface Container<T> {} |
Bounded Type (extends ) | Restrict types to subclasses | <T extends Number> |
Unbounded Wildcard (? ) | Accepts any type | List<?> |
Upper Bounded (? extends T ) | Accepts T and its subclasses | List<? extends Number> |
Lower Bounded (? super T ) | Accepts T and its superclasses | List<? super Integer> |