A Complete Illustrated Guide to Java Generics
This article explains why Java generics were introduced, demonstrates how they improve type safety and reduce casting through concrete examples with generic classes, interfaces, collections, custom generic methods, and highlights common pitfalls such as generic arrays and static methods.
Generics are a type that can accept other data types, introduced to solve the problem of unsafe collections that store Object instances, which can cause type‑mismatch errors at runtime.
1. Why Generics Were Introduced
When adding elements to a collection like ArrayList, the add() method accepts Object. Adding a Boy object to a list intended for Person objects leads to runtime type errors during iteration. The traditional approach cannot enforce element types, requires explicit casts, and reduces program robustness.
2. Benefits of Using Generics
Improves program robustness and standardisation by letting the compiler detect type violations.
Provides compile‑time type checking, increasing safety.
Reduces the number of casts, improving efficiency.
Allows class declarations to specify attribute types, method return types, and parameter types.
When generics are not used, the compiler cannot guarantee type correctness, as illustrated by the following image:
When generics are used, type mismatches are caught at compile time:
3. Common Generic Usages
3.1 Defining a Generic Interface
interface Im<U, R> {
void hi(R r);
void hello(R r1, R r2, U u1, U u2);
default R method(U u) { return null; }
}If a class implements this interface with mismatched type arguments, the compiler reports an error, ensuring strict type conformity.
3.2 Generic Collections
Using a generic HashSet<Student> to store three Student objects eliminates the need for casts during iteration:
HashSet<Student> students = new HashSet<Student>();
students.add(new Student("Lazy", 21));
students.add(new Student("Happy", 41));
students.add(new Student("Beautiful", 13));
for (Student s : students) {
System.out.println(s);
}Similarly, a generic HashMap<String, Student> stores key‑value pairs without explicit casting:
HashMap<String, Student> hm = new HashMap<String, Student>();
hm.put("001", new Student("Happy", 21));
hm.put("002", new Student("Lazy", 32));
hm.put("003", new Student("Beautiful", 43));
Set<Map.Entry<String, Student>> entries = hm.entrySet();
for (Map.Entry<String, Student> e : entries) {
System.out.println(e.getKey() + " - " + e.getValue());
}When iterating a HashMap , entrySet() returns typed Map.Entry<K,V> objects, removing the need for manual casts.
4. Generic Details
4.1 Type Constraints in <>
4.2 Inheritance with Generics
After specifying a concrete type for a generic, you can pass that type or any subclass:
P<A> p1 = new P<A>(new A());
P<A> p2 = new P<A>(new B()); // B extends A4.3 Diamond Operator Shorthand
P<A> p = new P(new A());5. Custom Generics
5.1 Class‑Level Generic Parameters
class U<X, Y, Z> {
public void hi(X x, Y y) { /* ... */ }
}Usage:
U<String, Double, Integer> u = new U<>();
u.hi("hello", 1.0); // X → String, Y → Double5.2 Generic Methods
class U<X, Y, Z> {
public <X, Y> void m1(X x, Y y) { /* ... */ }
}When calling m1, the compiler infers the concrete types and performs autoboxing automatically.
6. Common Pitfalls
Generic arrays cannot be instantiated because the component type is unknown at runtime. Example of illegal code: A[] a = new A[]; Static methods cannot use the class’s generic type parameters because static members belong to the class, not to any instance; the JVM cannot resolve the type during class loading.
Author: 懒羊羊.java (iyu77.blog.csdn.net/article/details/125304857)
SpringMeng
Focused on software development, sharing source code and tutorials for various systems.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
