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.
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émNegativeAgeException(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émInvalidAgeException(có kiểm tra) với thông báo"Must be 18 or older: [age]". Nếu không, hãy in raAge [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áothrows 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émNegativeAgeExceptionvới định dạng thông báo tương tự. Nếu độ tuổi nhỏ hơn 18, hãy némIllegalArgumentExceptionvới thông báo"Too young: [age]". Nếu không, hãy in raAge [age] verified (unchecked method). Lưu ý, phương thức này không cần khai báothrows!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ọiverifyAgevớ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ắtInvalidAgeExceptionvà in raChecked exception caught: [message]. Đồng thời, bắtNegativeAgeExceptionvà in raUnchecked exception caught: [message].Sau đó, hãy in một dòng trống và
=== Unchecked Exception Method ===. GọiverifyAgeUncheckedOnlyvớ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ắtNegativeAgeExceptionvà in raRuntime exception caught: [message]. BắtIllegalArgumentExceptionvà in raIllegal 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]"
}
}
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
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh10Xử lý ngoại lệ trong OOP
Phân cấp lớp ngoại lệNgoại lệ tùy chỉnhLỗi có kiểm tra và không kiểm traMẫu try-with-resourcesÔn tập: người dùng đã xác thực2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online