9.3 Working with Generics
Overview of Generics
Generics in Java allow developers to specify types when defining classes, interfaces, and methods. Generics enhance code safety by enabling type checks at compile-time, reducing runtime errors. They also help in creating reusable and type-safe collections.
Key Benefits of Generics
- Type Safety: Prevents ClassCastException by enforcing a specific type at compile-time.
- Code Reusability: Enables creating methods and classes that work with various types.
- Improved Readability: Enhances the clarity of the code by specifying data types.
Using Generics in Collections
Java Collections Framework is one of the most common applications of generics. Collections like List, Set, and Map use generics to specify the type of objects they hold.
Example: Generic List with Type-Safe Operations
import java.util.ArrayList;
import java.util.List;
public class GenericsExample {
public static void main(String[] args) {
// Creating a List with Generics
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// This line would cause a compile-time error, enforcing type safety
// names.add(10);
// Accessing elements without casting
for (String name : names) {
System.out.println(name);
}
}
}
In this example, a List is created with a generic type <String>. Trying to add an element of a different type (e.g., Integer) would cause a compile-time error.
Generic Classes
A generic class defines one or more type parameters, allowing it to be used with different types. You specify the generic type parameter when instantiating the class.
Example: Creating a Generic Class
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>("Hello");
Box<Integer> integerBox = new Box<>(123);
System.out.println("String Box: " + stringBox.getContent()); // Output: Hello
System.out.println("Integer Box: " + integerBox.getContent()); // Output: 123
}
}
In this example, we define a generic class Box<T>, where T represents the type. We can use Box with different types (String, Integer, etc.), making it versatile and type-safe.
Generic Methods
A generic method allows specifying generic parameters for individual methods, independent of the class's generic type.
Example: Generic Method to Print Arrays
public class GenericsMethodExample {
// Generic method to print any type of array
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"A", "B", "C"};
printArray(intArray); // Output: 1 2 3 4 5
printArray(stringArray); // Output: A B C
}
}
In this example, <T> is a type parameter for the printArray method. This method can accept arrays of any type, making it reusable for different data types.
Bounded Type Parameters
Bounded type parameters restrict the types that can be used with generics, allowing only specific classes or interfaces as the type argument.
Example: Bounded Type with Comparable
public class BoundedExample {
// Bounded type parameter with Comparable
public static <T extends Comparable<T>> T findMax(T x, T y) {
return x.compareTo(y) > 0 ? x : y;
}
public static void main(String[] args) {
System.out.println(findMax(10, 20)); // Output: 20
System.out.println(findMax("Apple", "Banana")); // Output: Banana
}
}
In this example, the <T extends Comparable<T>> constraint ensures that only types implementing the Comparable interface can be used with the findMax method.
Summary
- Generics enable type safety, code reusability, and improve readability in Java.
- They are commonly used with the Java Collections Framework to ensure collections hold specific types.
- Generic classes and generic methods provide flexibility in defining reusable code components.
- Bounded type parameters restrict the types that can be used with generics, allowing for specific interfaces or classes only.
Generics are a powerful feature in Java that promotes clean, maintainable, and reliable code, especially when working with collections and reusable components.