Why Use Java Generics?¶
In Java, we often need to handle various data collections, such as storing student information or product details. Without generics, directly using the Object type to store data, while flexible, brings two main issues: type unsafety and cumbersome type casting.
For example, without generics, you might write:
// Collection without generics, storing Object type
List list = new ArrayList();
list.add("Zhang San");
list.add(20); // Accidentally added an integer type
// Casting is required when retrieving data, which is error-prone
String name = (String) list.get(0); // Correct
Integer age = (Integer) list.get(1); // Correct
String wrong = (String) list.get(1); // Runtime ClassCastException!
The problem here is that list can store any type of data. The compiler cannot check type matches in advance, and exceptions will be thrown at runtime if types mismatch.
What Are Generics?¶
Generics, introduced in Java 5, allow us to pass types as parameters to classes, interfaces, or methods, enabling type safety and code reuse. In simple terms, generics are “parameterized types”—for example, List<String> represents a list that can only store String types.
How to Use Generics?¶
1. Generic Classes¶
Generic classes are the most basic application of generics. Define them by declaring a type parameter (e.g., <T>) after the class name. T is a placeholder (customizable names like E, K, V are also common).
Example: Define a General “Box” Class
class Box<T> {
private T content; // Use T as the type parameter
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
Using the Generic Class
// Box storing Strings
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String str = stringBox.getContent(); // No casting needed, returns String directly
// Box storing Integers
Box<Integer> intBox = new Box<>();
intBox.setContent(100);
Integer num = intBox.getContent(); // Returns Integer directly
Generics allow the Box class to flexibly store any type of data. The compiler checks type matches at compile time, avoiding runtime errors.
2. Generic Interfaces¶
Generic interfaces are similar to generic classes. Declare type parameters after the interface name, and implementing classes must specify concrete types.
Example: Define a “Generator” Interface
interface Generator<T> {
T generate(); // Interface method returns type T
}
// Implementing the generic interface (specify concrete type)
class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "Generic String";
}
}
// Usage
Generator<String> generator = new StringGenerator();
String result = generator.generate(); // Returns "Generic String"
3. Generic Methods¶
Generic methods are methods that have their own type parameters, applicable in both ordinary and generic classes.
Example: Define a General “Print” Method
class Util {
// Generic method: Accepts an array of any type, returns the first element
public static <T> T getFirstElement(T[] array) {
if (array.length == 0) return null;
return array[0];
}
}
// Usage
String[] strArray = {"A", "B", "C"};
String firstStr = Util.getFirstElement(strArray); // Returns "A"
Integer[] intArray = {1, 2, 3};
Integer firstInt = Util.getFirstElement(intArray); // Returns 1
4. Generic Collections (Most Common)¶
Java standard library collections (e.g., ArrayList, HashMap) support generics, a typical application scenario.
Example: Using Generic Collections
// ArrayList storing String types
List<String> names = new ArrayList<>();
names.add("Alice"); // Correct
// names.add(123); // Compilation error: Type mismatch, only String allowed
names.forEach(System.out::println); // Iterate and print: Alice
// HashMap storing key-value pairs (key: String, value: Integer)
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90); // Correct
scores.put("Bob", 85);
// scores.put("Charlie", "95"); // Compilation error: Value must be Integer
Integer aliceScore = scores.get("Alice"); // No casting needed, returns Integer directly
Wildcards: Making Generics More Flexible¶
The wildcard <?> handles the type range of generic collections, with two common forms:
- Upper Bounded Wildcard:
<? extends T>
Indicates elements in the collection can only beTor its subclasses (withTas the upper bound).
Example: Allows storingNumberand its subclasses (e.g.,Integer,Double).
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(new Integer(10)); // Error! Cannot add elements
Number num = numbers.get(0); // Correct, returns Number type
- Lower Bounded Wildcard:
<? super T>
Indicates elements in the collection can only beTor its superclasses (withTas the lower bound).
Example: Allows storingIntegerand its superclasses (e.g.,Number,Object).
List<? super Integer> ints = new ArrayList<Object>();
ints.add(new Integer(10)); // Correct
Object obj = ints.get(0); // Correct, returns Object type
Core Advantages of Generics¶
- Type Safety: Type checks at compile time prevent
ClassCastExceptionat runtime. - Eliminates Casting: No manual casting required, resulting in cleaner code.
- Code Reusability: Implement a single logic to handle multiple types via type parameters, avoiding redundant code.
Precautions¶
- Primitive Types Not Allowed: Generic types must be reference types (e.g., use
Integerinstead ofint).
List<int> nums = new ArrayList<>(); // Error! Use List<Integer> instead
- Generics Do Not Support Inheritance:
List<String>andList<Object>are independent and cannot be assigned to each other.
List<String> strList = new ArrayList<>();
List<Object> objList = strList; // Error: Generics are not inheritable
- Type Erasure: Generic types are erased at runtime (e.g.,
Tis replaced withObject). Thus, you cannot directly instantiateT(e.g.,new T()).
Summary¶
Generics are a crucial Java feature for ensuring type safety and code reuse. By parameterizing types, they make collections and classes more flexible and secure. Mastering generics involves understanding type parameters, generic classes/methods/interfaces, and wildcards. Start with common collection generics, then gradually practice generic classes and methods to improve code quality.
Through the examples and explanations above, you should now understand the value and usage of Java generics. Generics simplify code and reduce errors, making it a powerful tool for writing robust Java programs!