Menu
CoddyTech

Lỗi có kiểm tra và không kiểm tra

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 61 trên 87.

Bạn đã thấy hệ thống phân cấp ngoại lệ (exception) của Java chia thành nhiều nhánh. Điểm khác biệt chính bạn cần hiểu là giữa ngoại lệ được kiểm tra (checked) và ngoại lệ không được kiểm tra (unchecked). Điều này ảnh hưởng đến cách bạn phải xử lý chúng trong code.

Ngoại lệ được kiểm tra là những ngoại lệ mà trình biên dịch buộc bạn phải xử lý. Chúng kế thừa trực tiếp từ Exception (không thông qua RuntimeException). Bạn phải bắt chúng hoặc khai báo chúng bằng throws:

// Phải xử lý IOException vì đây là ngoại lệ (exception) được kiểm tra
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);  // Có thể ném ra IOException
}

// Hoặc bắt ngoại lệ đó
public void readFileSafe(String path) {
    try {
        FileReader reader = new FileReader(path);
    } catch (IOException e) {
        System.out.println("File error: " + e.getMessage());
    }
}

Ngoại lệ không cần kiểm tra kế thừa RuntimeException. Trình biên dịch không yêu cầu bạn xử lý chúng. Chúng thường chỉ ra lỗi lập trình như tham chiếu null hoặc truy cập mảng không hợp lệ:

// Không cần khai báo throws
public int divide(int a, int b) {
    return a / b;  // Có thể phát sinh ngoại lệ ArithmeticException (không kiểm tra)
}

String name = null;
name.length();  // NullPointerException (không kiểm tra)

Khi tạo ngoại lệ tùy chỉnh, hãy chọn lớp cha dựa trên cách bạn muốn bên gọi xử lý chúng. Dùng ngoại lệ được kiểm tra (checked exception) cho những tình huống có thể khắc phục mà bên gọi nên lường trước. Dùng ngoại lệ không được kiểm tra (unchecked exception) cho những lỗi lập trình không nên xảy ra trong code đúng.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống xác minh độ tuổi để minh họa điểm khác biệt chính giữa ngoại lệ có kiểm tra và ngoại lệ không kiểm tra! Bạn sẽ tạo các ngoại lệ tùy chỉnh thuộc cả hai loại và xem trình biên dịch xử lý chúng khác nhau như thế nào.

Bạn sẽ sắp xếp code trong bốn tệp:

  • InvalidAgeException.java: Tạo một ngoại lệ có kiểm tra cho các trường hợp xác minh độ tuổi không thành công. Ngoại lệ này cần kế thừa trực tiếp từ Exception, nghĩa là bên gọi sẽ buộc phải xử lý nó. Thêm hàm khởi tạo nhận một thông báo và truyền thông báo đó cho lớp cha.
  • NegativeAgeException.java: Tạo một ngoại lệ không kiểm tra cho trường hợp ai đó cung cấp độ tuổi âm (lỗi lập trình không nên xảy ra nếu đầu vào chính xác). Ngoại lệ này cần kế thừa từ RuntimeException. Thêm hàm khởi tạo nhận một thông báo và truyền thông báo đó cho lớp cha.
  • AgeVerifier.java: Tạo một lớp xác minh độ tuổi bằng cả hai loại ngoại lệ. Thêm hai phương thức tĩnh:

    verifyAge(int age) - Phương thức này cần ném NegativeAgeException (không kiểm tra) với thông báo "Age cannot be negative: [age]" nếu độ tuổi nhỏ hơn 0. Nếu độ tuổi nhỏ hơn 18, hãy ném InvalidAgeException (có kiểm tra) với thông báo "Must be 18 or older: [age]". Nếu không, hãy in ra Age [age] verified successfully. Vì phương thức này ném một ngoại lệ có kiểm tra, bạn phải khai báo throws InvalidAgeException.

    verifyAgeUncheckedOnly(int age) - Phương thức này chỉ dùng ngoại lệ không kiểm tra. Nếu độ tuổi âm, hãy ném NegativeAgeException với định dạng thông báo tương tự. Nếu độ tuổi nhỏ hơn 18, hãy ném IllegalArgumentException với thông báo "Too young: [age]". Nếu không, hãy in ra Age [age] verified (unchecked method). Lưu ý, phương thức này không cần khai báo throws!

  • Main.java: Kết hợp hệ thống xác minh của bạn! Bạn sẽ nhận một đầu vào: độ tuổi (số nguyên).

    Trước tiên, hãy in ra === Checked Exception Method === và gọi verifyAge với độ tuổi đầu vào. Vì phương thức này ném một ngoại lệ có kiểm tra, bạn phải đặt lời gọi trong khối try-catch. Bắt InvalidAgeException và in ra Checked exception caught: [message]. Đồng thời, bắt NegativeAgeException và in ra Unchecked exception caught: [message].

    Sau đó, hãy in một dòng trống và === Unchecked Exception Method ===. Gọi verifyAgeUncheckedOnly với cùng độ tuổi. Dù phương thức này có thể ném ngoại lệ, bạn không bị buộc phải bắt chúng. Tuy nhiên, hãy đặt lời gọi trong khối try-catch để xử lý lỗi một cách phù hợp. Bắt NegativeAgeException và in ra Runtime exception caught: [message]. Bắt IllegalArgumentException và in ra Illegal argument caught: [message].

Bạn sẽ nhận một đầu vào: một số nguyên biểu thị độ tuổi cần xác minh.

Lưu ý điểm khác biệt chính: phương thức có ngoại lệ được kiểm tra bắt buộc phải có khai báo throws và buộc bên gọi phải xử lý ngoại lệ đó, trong khi phương thức dùng ngoại lệ không kiểm tra vẫn biên dịch bình thường mà không cần xử lý ngoại lệ. Đây là điểm khác biệt cơ bản giữa ngoại lệ có kiểm tra và ngoại lệ không kiểm tra trong Java!

Tự làm thử

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int age = scanner.nextInt();
        
        // TODO: Kiểm tra phương thức xử lý ngoại lệ có kiểm tra
        // In ra: "=== Checked Exception Method ==="
        // Gọi AgeVerifier.verifyAge(age) trong khối try-catch
        // Bắt InvalidAgeException: in ra "Checked exception caught: [message]"
        // Bắt NegativeAgeException: in ra "Unchecked exception caught: [message]"
        
        
        // TODO: In một dòng trống, rồi in "=== Unchecked Exception Method ==="
        // Gọi AgeVerifier.verifyAgeUncheckedOnly(age) trong khối try-catch
        // Bắt NegativeAgeException: in ra "Runtime exception caught: [message]"
        // Bắt IllegalArgumentException: in ra "Illegal argument caught: [message]"
        
    }
}
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