Bỏ qua để đến nội dung

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 sai

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 String

Thự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.

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à String
Integer firstNum = Utils.firstElement(List.of(1, 2, 3)); // T được suy luận là Integer

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à Number
System.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 Number

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"));
  • 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ể