Generics cho phép bạn viết class, interface, và method làm việc với một type parameter do người gọi chỉ định — mang lại type safety tại compile-time và loại bỏ việc ép kiểu (cast). Chúng là nền tảng của Collections Framework và các thư viện tái sử dụng được.
Vấn đề mà generics giải quyết
// ❌ trước khi có generics (hoặc dùng raw type) — không có type safety, cần cast
List list = new ArrayList();
list.add("hello");
list.add(42); // ối — bất cứ thứ gì cũng có thể bỏ vào
String s = (String) list.get(1); // ClassCastException tại RUNTIME! 💥
// ✅ với generics — kiểu được kiểm tra tại COMPILE time
List<String> list = new ArrayList<>();
list.add("hello");
list.add(42); // ❌ lỗi compile — bắt được ngay lập tức
String s = list.get(0); // không cần cast — đã biết chắc là String
Generics chuyển lỗi kiểu từ runtime (ClassCastException) sang compile time, và loại bỏ nhu cầu cast thủ công — giúp code an toàn và gọn gàng hơn.
Generic class và method
// một generic class — T là type parameter do người dùng điền vào
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> b = new Box<>(); // T = String
b.set("hi");
String x = b.get(); // có kiểu, không cần cast
// một generic method
public static <T> T firstElement(List<T> list) {
return list.get(0);
}
Bounded type parameter
// T phải là Number (hoặc subclass) — cho phép gọi các method của Number
public static <T extends Number> double sum(List<T> list) {
double total = 0;
for (T n : list) total += n.doubleValue(); // an toàn — T là một Number
return total;
}
<T extends X> ràng buộc type parameter, cho phép bạn dùng các thành viên của kiểu đó một cách an toàn.
Wildcard
List<? extends Number> nums; // "một subtype nào đó của Number" — đọc (producer)
List<? super Integer> sink; // "Integer hoặc một supertype" — ghi (consumer)
// PECS: Producer Extends, Consumer Super
Wildcard tăng tính linh hoạt cho các tham số method cần chấp nhận một dải các kiểu.
Type erasure (một lưu ý của Java)
// generics là tính năng tại COMPILE-TIME — bị xóa tại runtime (để tương thích ngược)
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass(); // true — cả hai chỉ là ArrayList tại runtime
// hệ quả: không thể làm `new T()` hay `instanceof List<String>`
Java cài đặt generics qua type erasure — các type parameter tồn tại tại compile time nhưng bị xóa tại runtime, điều này dẫn đến một số hạn chế.
Tại sao điều này quan trọng
Generics là thiết yếu với Java hiện đại — chúng mang lại type safety tại compile-time (bắt lỗi kiểu sớm thay vì để xảy ra ClassCastException lúc runtime), loại bỏ các cast dài dòng và dễ sai, và cho phép viết code tái sử dụng được, linh hoạt về kiểu.
Chúng là nền tảng của toàn bộ Collections Framework (List<String>, Map<K,V>) và vô số thư viện.
Hiểu generic class/method, bounded parameter, wildcard (PECS), và lưu ý về type-erasure giúp bạn vừa dùng đúng các API generic vừa thiết kế được các trừu tượng tái sử dụng, an toàn về kiểu của riêng mình — một kỹ năng cốt lõi để viết Java mạnh mẽ, dễ bảo trì.
