Tổng quan
Ở các phần trước, chúng ta đã làm quen với những kiến thức nền tảng về Java cũng như các nguyên lý lập trình hướng đối tượng. Trong phần này, chúng ta sẽ tìm hiểu về cơ chế xử lý ngoại lệ và các thư viện thường được sử dụng trong quá trình phát triển ứng dụng. Đây là những yếu tố then chốt giúp code trở nên ổn định và dễ bảo trì hơn.
1. Cấu trúc phân cấp ngoại lệ trong Java
1.1 Phân loại ngoại lệ
Java phân ngoại lệ thành hai nhóm chính: Error và Exception.
- Error: Đại diện cho các lỗi nghiêm trọng mà JVM không thể khắc phục được, chẳng hạn như
OutOfMemoryError(lỗi cạn kiệt bộ nhớ),StackOverflowError(lỗi tràn stack). Những lỗi này thường xuất phát từ việc hệ thống cạn kiệt tài nguyên hoặc logic chương trình có vấn đề nghiêm trọng, khi xảy ra chương trình sẽ buộc phải dừng lại. - Exception: Được chia thành hai loại con:
- Checked Exception: Bao gồm
IOException,SQLException. Những ngoại lệ này bắt buộc phải được xử lý trong quá trình biên dịch, thông qua cơ chế bắt giữ bằngtry-catchhoặc khai báo ném ra ngoài bằng từ khóathrows. - Unchecked Exception: Ví dụ điển hình là
NullPointerException,ArrayIndexOutOfBoundsException. Loại ngoại lệ này không yêu cầu xử lý bắt buộc tại thời điểm biên dịch, nhưng nếu xảy ra trong runtime sẽ làm chương trình bị gián đoạn.
- Checked Exception: Bao gồm
1.2 Quan hệ kế thừa giữa các lớp ngoại lệ
Tất cả các lớp ngoại lệ đều thừa kế từ Throwable. Error và Exception là hai nhánh con trực tiếp, mỗi nhánh lại có nhiều lớp con cụ thể hơn, tạo thành một hệ thống phân cấp rõ ràng giúp lập trình viên có thể xử lý từng loại ngoại lệ một cách chính xác.
2. Cơ chế xử lý ngoại lệ
2.1 Cấu trúc try-catch
Đây là cách phổ biến nhất để bắt và xử lý ngoại lệ:
try {
// Đoạn code có khả năng ném ra ngoại lệ
BufferedReader reader = new BufferedReader(new FileReader("dulieu.txt"));
String line = reader.readLine();
} catch (FileNotFoundException e) {
// Xử lý khi file không tồn tại
System.err.println("Lỗi truy cập file: " + e.getMessage());
} catch (IOException e) {
// Xử lý khi đọc file thất bại
System.err.println("Lỗi nhập xuất: " + e.getMessage());
}
Khi ngoại lệ xảy ra trong khối try, chương trình sẽ nhảy ngay đến khối catch tương ứng để thực thi logic xử lý, nhờ đó tránh được việc chương trình bị dừng đột ngột.
2.2 Khối finally
Khối finally chứa những đoạn code cần được thực thi bất kể có ngoại lệ xảy ra hay không, thường dùng để giải phóng tài nguyên:
Connection ketNoi = null;
try {
ketNoi = DriverManager.getConnection(url, user, password);
// Thực hiện các thao tác với database
} catch (SQLException e) {
System.err.println("Kết nối thất bại: " + e.getMessage());
} finally {
if (ketNoi != null) {
try {
ketNoi.close();
} catch (SQLException e) {
System.err.println("Lỗi khi đóng kết nối: " + e.getMessage());
}
}
}
2.3 Từ khóa throws
Khi một phương thức có thể ném ra ngoại lệ nhưng không muốn xử lý ngay tại đó, có thể sử dụng throws để chuyển việc xử lý cho phương thức gọi nó:
public void layDuLieu() throws IOException, SQLException {
Connection conn = DriverManager.getConnection(url, user, pass);
}
Phương thức gọi layDuLieu sẽ phải xử lý các ngoại lệ này bằng try-catch hoặc tiếp tục khai báo throws.
3. Các thư viện quan trọng trong Java
3.1 Các collection trong java.util
ArrayList: Cấu trúc mảng động cho phép thêm, xóa, sửa phần tử linh hoạt:
List<String> danhSach = new ArrayList<>();
danhSach.add("Java");
danhSach.add("Python");
danhSach.add("C++");
danhSach.remove(1); // Xóa phần tử tại vị trí index 1
String ngonNgu = danhSach.get(0);
HashMap: Lưu trữ dữ liệu theo cặp key-value, hỗ trợ tìm kiếm nhanh:
Map<String, Integer> tuDien = new HashMap<>();
tuDien.put("Apple", 1);
tuDien.put("Banana", 2);
Integer giaTri = tuDien.get("Apple");
if (tuDien.containsKey("Banana")) {
System.out.println("Tồn tại banana");
}
Collections: Cung cấp các phương thức tĩnh để thao tác với collection:
List<Integer> mangSo = new ArrayList<>();
mangSo.add(42);
mangSo.add(17);
mangSo.add(89);
Collections.sort(mangSo); // Sắp xếp tăng dần
Collections.reverse(mangSo); // Đảo ngược thứ tự
int viTriMin = Collections.min(mangSo);
3.2 Xử lý ngày tháng
Date: Lớp cổ điển để làm việc với ngày giờ, tuy nhiên đã không còn được khuyến khích sử dụng:
Date homNay = new Date();
System.out.println(homNay);
Calendar: Lớp trừu tượng cho phép thao tác với ngày tháng linh hoạt hơn:
Calendar lich = Calendar.getInstance();
int nam = lich.get(Calendar.YEAR);
int thang = lich.get(Calendar.MONTH) + 1; // Tháng trong Calendar bắt đầu từ 0
int ngay = lich.get(Calendar.DAY_OF_MONTH);
lich.add(Calendar.HOUR, 5); // Cộng thêm 5 giờ
LocalDateTime (Java 8+): Giải pháp hiện đại, an toàn với thread và dễ sử dụng:
LocalDateTime hienTai = LocalDateTime.now();
LocalDateTime ngayMai = hienTai.plusDays(1);
LocalDateTime sau2Gio = hienTai.plusHours(2);
String dinhDang = hienTai.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"));
4. Hướng phát triển tiếp theo
Để củng cố kiến thức, bạn nên thực hành xử lý ngoại lệ trong các tình huống thực tế như thao tác với file, kết nối mạng, hoặc làm việc với cơ sở dữ liệu. Hãy thử nghiệm với nhiều cách xử lý khác nhau để hiểu rõ khi nào nên bắt giữ ngoại lệ cụ thể, khi nào nên để nó lan truyền lên trên.
Với các thư viện đã giới thiệu, hãy dành thời gian đọc tài liệu chính thức để khám phá thêm các phương thức hữu ích. Bạn có thể viết một ứng dụng quản lý công việc đơn giản để luyện tập việc sử dụng ArrayList, HashMap, cũng như xử lý ngày tháng với LocalDateTime.
Hãy rà soát lại các đoạn code đã viết trước đó và tìm cách cải thiện bằng cách áp dụng các thư viện phù hợp cũng như xử lý ngoại lệ tốt hơn.