CodeGym /Các khóa học /JAVA 25 SELF /Generics: vì sao cần, cú pháp cơ bản

Generics: vì sao cần, cú pháp cơ bản

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Vấn đề của collection “thô” (raw types)

Hãy lùi lại một chút về lịch sử. Trước Java 5, tất cả collection đều “ăn tạp”. Chúng lưu các đối tượng kiểu Object, và trình biên dịch không kiểm soát bạn nhét gì vào. Muốn đưa chuỗi vào? Xin mời. Số à? Tại sao không. Con mèo? Cũng được nốt.

// Ví dụ về collection "thô" (raw types), Java trước phiên bản 5
List list = new ArrayList();
list.add("Xin chào");
list.add(42);
list.add(new Object());

Vấn đề lộ ra khi “lấy ra” và sử dụng giá trị:

String s = (String) list.get(0); // OK, đây là chuỗi
String s2 = (String) list.get(1); // BOOM! ClassCastException

Trình biên dịch im lặng, còn khi chạy thì bạn nhận ClassCastException. Giống như một chiếc hộp ghi “táo”, bên trong lại là một cái cốc, quả chuối và con nhím.

Tại sao điều này tệ?

  • Lỗi chỉ xuất hiện khi chạy (runtime).
  • Kiểu bị lẫn lộn: bạn phải tự ép đối tượng về kiểu mong muốn (cast).
  • Mã khó đọc hơn và nguy hiểm hơn.

Giải pháp — generics (tổng quát hóa)

Generics là cơ chế cho phép tạo lớp, interface và phương thức có tham số kiểu. Tức là bạn nói với collection: “Chỉ lưu chuỗi”, và trình biên dịch sẽ giám sát nghiêm ngặt điều đó.

List<String> words = new ArrayList<>();
words.add("Xin chào");
words.add("Thế giới");
// words.add(42); // Lỗi biên dịch! Không thể thêm int vào List<String>

Giờ trình biên dịch sẽ không cho phép đưa vào danh sách bất cứ thứ gì ngoài chuỗi. Lỗi được bắt trước khi chạy chương trình.

Ý tưởng chính của generics:
Đảm bảo an toàn kiểu cho collection (và không chỉ collection), để lỗi được bắt ở giai đoạn biên dịch, chứ không phải tại runtime.

2. Cú pháp generics: trông như thế nào trong mã

Khai báo kiểu trong dấu ngoặc nhọn

Khi tạo collection, hãy chỉ định kiểu phần tử trong <>:

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // Lỗi: không thể thêm số vào danh sách chuỗi

String first = names.get(0); // Không cần ép kiểu!

Kinh điển:

  • List<String> — danh sách chuỗi
  • List<Integer> — danh sách số nguyên
  • Set<Double> — tập hợp số thực
  • Map<String, Integer> — khóa String, giá trị Integer

Tại sao không nên chỉ viết List?

Có thể, nhưng bạn sẽ mất hết lợi ích của generics, và trình biên dịch sẽ cảnh báo:

List list = new ArrayList(); // raw type — không khuyến nghị!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Chào mừng, ClassCastException!

Mã Java hiện đại luôn dùng generics.

Toán tử diamond <>

Từ Java 7, có thể không chỉ định kiểu ở vế phải nếu ngữ cảnh đã rõ:

List<String> list = new ArrayList<>(); // Trình biên dịch tự suy ra rằng đây là <String>

3. Generics cho các collection khác nhau

Ví dụ cho List, Set, Map

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 23);
ages.put("Bob", 31);

Ví dụ với lớp tự định nghĩa

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. Những điểm hữu ích

Ưu điểm của generics

An toàn kiểu. Trình biên dịch đảm bảo chỉ các phần tử đúng kiểu mới được đưa vào collection.

Không cần ép kiểu. Trước đây: String s = (String) list.get(0);. Bây giờ: String s = list.get(0);.

Mã dễ đọc và tin cậy hơn. Ít bất ngờ hơn khi chạy.

Hạn chế của generics

Không thể dùng kiểu nguyên thủy. Generics chỉ làm việc với đối tượng, không phải với kiểu nguyên thủy (int, double, boolean). Hãy dùng các lớp bao: Integer, Double, Boolean.

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int sẽ tự động chuyển thành Integer (autoboxing)

Tóm lược về xóa kiểu (type erasure)

Trong Java, generics được hiện thực thông qua cơ chế xóa kiểu: sau khi biên dịch, thông tin về tham số kiểu bị xóa, và tại runtime JVM không biết đó từng là List<String> chứ không chỉ là List. Điều này nhằm giữ tương thích ngược.

Hệ quả: không thể kiểm tra tham số kiểu bằng instanceof với đối số kiểu cụ thể.

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Lỗi biên dịch!

Cố gắng thêm phần tử kiểu khác — lỗi biên dịch

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Lỗi biên dịch: incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("Mèo", 5);
// map.put(3, "Voi"); // Lỗi: khóa phải là String, giá trị — Integer

Và đó là điều tuyệt vời: lỗi được bắt ở giai đoạn biên dịch.

Không chỉ collection

Generics có thể dùng trong các lớp và phương thức tự định nghĩa. Ví dụ, “Hộp” dùng chung:

class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("Xin chào");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

Trong collection, generics là tiêu chuẩn, nhưng bạn cũng sẽ gặp chúng ở nơi khác, ví dụ trong Stream APIOptional.

5. Lỗi thường gặp khi làm việc với generics

Lỗi số 1: Dùng collection “thô”. Cách viết như List list = new ArrayList(); làm mất an toàn kiểu. Hãy luôn chỉ định tham số kiểu, ví dụ List<String>.

Lỗi số 2: Cố dùng kiểu nguyên thủy. Không thể viết List<int>, hãy dùng List<Integer>.

Lỗi số 3: Ép kiểu thủ công khi đọc từ collection. Nếu bạn dùng generics, việc ép kiểu như (String) list.get(i) là không cần thiết. Nếu vẫn phải ép kiểu — tức là đâu đó bạn đã làm sai về kiểu.

Lỗi số 4: Kỳ vọng rằng tham số kiểu có sẵn ở runtime. Do xóa kiểu, không thể kiểm tra chúng bằng instanceof theo kiểu List<String>.

Lỗi số 5: Trộn nhiều kiểu trong cùng một collection. Nếu đã khai báo List<String>, đừng cố thêm Integer — trình biên dịch sẽ không cho phép, và đó là điều tốt.

1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Bộ sưu tập và generics
Bộ sưu tập và generics
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION