Java Generics
What are Java Generics?
Java Generics enable you to write flexible and reusable code by allowing classes, interfaces, and methods to operate on different data types while maintaining type safety.
Instead of hardcoding a specific data type, generics use type parameters (such as <T>) as placeholders. The actual type is specified when the code is used.
Why Use Generics in Java?
Generics offer several important advantages:
- Code Reusability: Write once, use with multiple data types
- Type Safety: Detect errors at compile time instead of runtime
- Eliminates Casting: No need for explicit type casting
- Cleaner Code: Improves readability and maintainability
Creating a Generic Class
A generic class allows you to define a class that can work with any data type.
Example: Generic Container Class
class Container<T> {
private T data;
public void store(T data) {
this.data = data;
}
public T retrieve() {
return data;
}
}
public class GenericClassDemo {
public static void main(String[] args) {
// Using Container with String
Container<String> messageBox = new Container<>();
messageBox.store("Welcome to Generics");
System.out.println("Message: " + messageBox.retrieve());
// Using Container with Double
Container<Double> priceBox = new Container<>();
priceBox.store(299.99);
System.out.println("Price: " + priceBox.retrieve());
}
}
How It Works
- <T> is a type parameter (placeholder for any data type)
- When you create Container<String>, T becomes String
- When you create Container<Double>, T becomes Double
This allows the same class to work with different data types without rewriting code.
Generic Methods in Java
Generics can also be applied to methods, enabling them to operate on various data types independently of the class.
Example: Generic Method for Displaying Elements
public class GenericMethodDemo {
// Generic method
public static <T> void displayElements(T[] elements) {
for (T element : elements) {
System.out.println(element);
}
}
public static void main(String[] args) {
String[] courses = {"Java", "Python", "AI"};
Integer[] marks = {85, 90, 95};
displayElements(courses);
displayElements(marks);
}
}
Key Points
- <T> before the return type defines a generic method
- The method works with any data type
- Java automatically determines the type at runtime based on input
Bounded Generics (Restricted Types)
Sometimes, you may want to restrict the types that a generic class or method can accept. This is done using bounded types with the extends keyword.
Example: Working with Numeric Types Only
class Calculator<T extends Number> {
private T[] values;
public Calculator(T[] values) {
this.values = values;
}
public double computeAverage() {
double total = 0.0;
for (T value : values) {
total += value.doubleValue();
}
return total / values.length;
}
}
public class BoundedGenericDemo {
public static void main(String[] args) {
Integer[] intValues = {10, 20, 30};
Calculator<Integer> intCalc = new Calculator<>(intValues);
System.out.println("Integer Average: " + intCalc.computeAverage());
Float[] floatValues = {2.5f, 3.5f, 4.5f};
Calculator<Float> floatCalc = new Calculator<>(floatValues);
System.out.println("Float Average: " + floatCalc.computeAverage());
}
}
Explanation
- <T extends Number> restricts T to numeric types like Integer, Double, or Float
- doubleValue() converts any numeric type into a double for calculation
- Ensures type safety while allowing flexibility
Generics in Java Collections
The Java Collections Framework uses generics extensively. This ensures that collections store only a specific type of data.
Example: Using Generics with ArrayList
import java.util.ArrayList;
public class GenericCollectionExample {
public static void main(String[] args) {
ArrayList<String> products = new ArrayList<>();
products.add("Laptop");
products.add("Tablet");
// No casting required
String item = products.get(0);
System.out.println("First product: " + item);
}
}