Menu
CoddyTech

Giới thiệu về kiểu tổng quát

Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 53 trên 87.

Hãy tưởng tượng bạn cần tạo một lớp lưu trữ giá trị và lấy lại giá trị đó sau này. Nếu không dùng kiểu tổng quát, bạn sẽ phải viết các lớp riêng cho từng kiểu dữ liệu: một lớp cho Integer, một lớp cho String, một lớp cho Double, v.v. Điều này khiến code bị lặp và gây khó khăn khi bảo trì.

Generics giải quyết vấn đề này bằng cách cho phép bạn viết các lớp và phương thức hoạt động với mọi kiểu dữ liệu mà vẫn đảm bảo an toàn kiểu. Bạn định nghĩa một chỗ giữ chỗ (gọi là tham số kiểu), rồi chỗ đó sẽ được thay bằng một kiểu dữ liệu thực tế khi bạn dùng lớp:

// Không dùng generics: dùng Object, cần ép kiểu
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // Cần ép kiểu thủ công

// Dùng generics: an toàn về kiểu dữ liệu, không cần ép kiểu
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // Không cần ép kiểu

Dấu ngoặc nhọn <T> biểu thị một tham số kiểu dữ liệu. Khi viết List<String>, bạn đang cho trình biên dịch biết rằng danh sách này chỉ chứa các chuỗi. Nếu bạn thử thêm một số nguyên, trình biên dịch sẽ phát hiện lỗi ngay lập tức thay vì để chương trình gặp lỗi khi chạy.

Generics mang lại hai lợi ích chính: chúng loại bỏ nhu cầu ép kiểu tường minh và phát hiện lỗi kiểu dữ liệu khi biên dịch thay vì khi chạy. Nhờ đó, code của bạn an toàn và gọn gàng hơn. Trong các bài học tiếp theo, chúng ta sẽ tìm hiểu cách tạo lớp và phương thức generic của riêng bạn.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống lưu trữ đơn giản để minh họa sức mạnh của kiểu tổng quát (generics) bằng cách so sánh cách cũ (dùng Object cùng với ép kiểu) và cách mới (dùng kiểu tổng quát để đảm bảo an toàn kiểu dữ liệu).

Bạn sẽ tạo ba tệp để tự mình thấy sự khác biệt:

  • OldBox.java: Tạo một lớp lưu trữ giá trị theo cách trước khi có kiểu tổng quát. OldBox cần có một trường private content thuộc kiểu Object. Thêm hàm khởi tạo nhận một Object và lưu trữ nó, cùng phương thức getContent() trả về Object. Cách này hoạt động, nhưng bạn cần ép kiểu khi lấy giá trị.
  • GenericBox.java: Bây giờ, hãy tạo phiên bản hiện đại, an toàn về kiểu dữ liệu bằng cách dùng kiểu tổng quát. GenericBox cần dùng tham số kiểu T trong phần khai báo. Lớp cần có một trường private content thuộc kiểu T, một hàm khởi tạo nhận giá trị T và phương thức getContent() trả về kiểu T. Bạn không cần ép kiểu khi dùng lớp này!
  • Main.java: Minh họa cả hai cách cạnh nhau. Bạn sẽ nhận hai đầu vào: một từ (String) và một số (số nguyên).

    Trước tiên, dùng cách cũ: tạo một OldBox lưu trữ từ đó, sau đó lấy giá trị bằng cách ép kiểu sang String và in ra: OldBox (with cast): [value]

    Tiếp theo, dùng cách tổng quát: tạo một GenericBox<String> lưu trữ cùng từ đó, lấy giá trị mà không ép kiểu và in ra: GenericBox (no cast): [value]

    Sau đó, cho thấy kiểu tổng quát hoạt động với số nguyên: tạo một GenericBox<Integer> lưu trữ số đó, lấy giá trị và in ra: GenericBox Integer: [value]

    Cuối cùng, in một dòng trống rồi in: Type safety: Generics catch errors at compile time!

Bạn sẽ nhận hai đầu vào theo thứ tự: một từ (String) và một số (số nguyên ở dạng String, bạn sẽ phân tích số này).

Hãy chú ý đến điểm khác biệt chính: với OldBox, bạn phải ép kiểu bằng (String) box.getContent() và hy vọng kiểu dữ liệu nhận được là chính xác. Với GenericBox, trình biên dịch biết kiểu dữ liệu nên bạn không cần ép kiểu. Đây là lợi ích cốt lõi của kiểu tổng quát: đảm bảo an toàn kiểu dữ liệu mà không cần phiền phức với việc ép kiểu!

Tự làm thử

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String word = scanner.nextLine();
        int number = Integer.parseInt(scanner.nextLine());
        
        // TODO: Tạo OldBox lưu từ đó
        // Lấy giá trị, ép kiểu sang String rồi in ra: "OldBox (with cast): [value]"
        
        // TODO: Tạo GenericBox<String> lưu từ đó
        // Lấy giá trị mà không cần ép kiểu rồi in ra: "GenericBox (no cast): [value]"
        
        // TODO: Tạo GenericBox<Integer> lưu số đó
        // Lấy giá trị rồi in ra: "GenericBox Integer: [value]"
        
        // TODO: In một dòng trống, sau đó in ra:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Java online