Generics
1. Vấn đề: viết code dùng chung cho nhiều kiểu
Phần tiêu đề “1. Vấn đề: viết code dùng chung cho nhiều kiểu”Không có generics, muốn viết một “hộp chứa” (box) dùng được cho mọi kiểu dữ liệu, phải dùng Object - mất đi tính an toàn kiểu (type safety), phải ép kiểu thủ công và dễ lỗi lúc chạy:
public class Box { private Object value; public void set(Object value) { this.value = value; } public Object get() { return value; }}
Box box = new Box();box.set("hello");String s = (String) box.get(); // phải ép kiểu thủ công - dễ ClassCastException nếu sai2. Generic class
Phần tiêu đề “2. Generic class”Với generics, kiểu dữ liệu trở thành một tham số (thường ký hiệu T), kiểm tra kiểu ngay lúc biên dịch:
public class Box<T> { // T là tham số kiểu (type parameter) private T value; public void set(T value) { this.value = value; } public T get() { return value; }}
Box<String> stringBox = new Box<>();stringBox.set("hello");String s = stringBox.get(); // KHÔNG cần ép kiểu - trình biên dịch đã biết chắc là String
// stringBox.set(123); // LỖI BIÊN DỊCH! Box<String> chỉ chứa StringThực ra bạn đã dùng generics từ trước khi biết tên nó - List<String>, Map<String, Integer> chính là các generic class có sẵn trong Java.
3. Generic method
Phần tiêu đề “3. Generic method”Một phương thức riêng lẻ cũng có thể có tham số kiểu riêng, độc lập với kiểu của class chứa nó:
public class Utils { static <T> T firstElement(List<T> list) { // <T> khai báo tham số kiểu cho phương thức này return list.get(0); }}
String first = Utils.firstElement(List.of("a", "b", "c")); // T được suy luận là StringInteger firstNum = Utils.firstElement(List.of(1, 2, 3)); // T được suy luận là Integer4. Bounded type parameter - giới hạn kiểu
Phần tiêu đề “4. Bounded type parameter - giới hạn kiểu”Có thể giới hạn T phải là kiểu con của một lớp/interface cụ thể bằng extends:
static <T extends Number> double sumAll(List<T> numbers) { // T phải là Number hoặc kiểu con của Number double total = 0; for (T n : numbers) { total += n.doubleValue(); } return total;}
System.out.println(sumAll(List.of(1, 2, 3))); // OK - Integer là NumberSystem.out.println(sumAll(List.of(1.5, 2.5))); // OK - Double là Number// sumAll(List.of("a", "b")); // LỖI BIÊN DỊCH! String không phải Number5. Wildcard ?
Phần tiêu đề “5. Wildcard ?”Khi chỉ cần đọc dữ liệu mà không quan tâm kiểu cụ thể, dùng ký hiệu đại diện ?:
static void printAll(List<?> list) { // chấp nhận List của BẤT KỲ kiểu nào for (Object item : list) { System.out.println(item); }}
printAll(List.of(1, 2, 3));printAll(List.of("a", "b", "c"));Tổng kết
Phần tiêu đề “Tổng kết”- Generics cho phép viết class/phương thức dùng chung cho nhiều kiểu mà vẫn kiểm tra kiểu lúc biên dịch
class Box<T>khai báo tham số kiểu cho class;static <T> T method(...)khai báo cho riêng phương thức<T extends Number>giới hạn kiểu phải thuộc một nhánh kế thừa cụ thểList<?>(wildcard) dùng khi chỉ cần đọc, không quan tâm kiểu phần tử cụ thể