Exception Handling trong Java
1. Phân Loại Exception
Exception trong Java được phân cấp theo một hệ thống kế thừa, với Throwable là lớp cơ sở. Dưới Throwable có hai nhánh chính:
1.1 Checked Exceptions
- Định nghĩa: Là các exception bắt buộc phải được xử lý tại thời điểm biên dịch
- Đặc điểm:
- Kế thừa từ class Exception
- Compiler bắt buộc phải handle bằng try-catch hoặc throws
- Thường đại diện cho các lỗi có thể phục hồi được
- Ví dụ phổ biến:
- IOException: Lỗi input/output
- SQLException: Lỗi truy cập database
- ClassNotFoundException: Không tìm thấy class
1.2 Unchecked Exceptions
- Định nghĩa: Là các exception không bắt buộc phải xử lý tại thời điểm biên dịch
- Đặc điểm:
- Kế thừa từ RuntimeException
- Compiler không bắt buộc phải handle
- Thường đại diện cho các lỗi lập trình
- Ví dụ phổ biến:
- NullPointerException: Truy cập null object
- ArrayIndexOutOfBoundsException: Truy cập index không hợp lệ
- IllegalArgumentException: Tham số không hợp lệ
1.3 Error
- Định nghĩa: Đại diện cho các lỗi nghiêm trọng của hệ thống
- Đặc điểm:
- Kế thừa trực tiếp từ Throwable
- Không nên cố gắng bắt và xử lý
- Thường là các lỗi không thể phục hồi
- Ví dụ phổ biến:
- OutOfMemoryError: Không đủ bộ nhớ heap
- StackOverflowError: Stack tràn
- VirtualMachineError: Lỗi JVM
2. Try-Catch-Finally
2.1 Cơ Bản
- Try Block:
- Chứa code có thể ném ra exception
- Có thể chứa nhiều câu lệnh
-
Phải được theo sau bởi catch hoặc finally
-
Catch Block:
- Bắt và xử lý exception
- Có thể có nhiều catch blocks
-
Thứ tự catch từ cụ thể đến tổng quát
-
Finally Block:
- Luôn được thực thi, bất kể có exception hay không
- Thường dùng để giải phóng tài nguyên
- Chạy sau cùng, kể cả khi có return trong try/catch
public class ExceptionDemo {
public void readFile(String path) {
FileReader reader = null;
try {
reader = new FileReader(path);
// Đọc file
} catch (FileNotFoundException e) {
System.err.println("File không tồn tại: " + e.getMessage());
} catch (IOException e) {
System.err.println("Lỗi đọc file: " + e.getMessage());
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
System.err.println("Lỗi đóng file: " + e.getMessage());
}
}
}
}
2.2 Try-with-Resources
- Định nghĩa:
- Tự động đóng tài nguyên sau khi sử dụng
- Tài nguyên phải implement AutoCloseable
-
Được giới thiệu từ Java 7
-
Ưu điểm:
- Code gọn gàng hơn
- Tự động gọi close() theo thứ tự ngược lại
- Xử lý exception khi đóng tài nguyên
public class TryWithResourcesDemo {
public void readFile(String path) {
try (FileReader reader = new FileReader(path);
BufferedReader br = new BufferedReader(reader)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Lỗi đọc file: " + e.getMessage());
}
// Resources tự động được đóng
}
}
3. Custom Exceptions
3.1 Tạo Exception Tùy Chỉnh
- Mục đích:
- Tạo exception phù hợp với nghiệp vụ
- Cung cấp thông tin chi tiết về lỗi
-
Phân biệt các loại lỗi khác nhau
-
Quy tắc:
- Kế thừa từ Exception hoặc RuntimeException
- Đặt tên có hậu tố Exception
- Cung cấp constructors phù hợp
public class BusinessException extends Exception {
private final String errorCode;
public BusinessException(String message, String errorCode) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
public class ValidationException extends RuntimeException {
private final List<String> violations;
public ValidationException(String message, List<String> violations) {
super(message);
this.violations = violations;
}
public List<String> getViolations() {
return violations;
}
}
3.2 Sử Dụng Custom Exception
- Khi nào sử dụng:
- Cần thông tin chi tiết về lỗi nghiệp vụ
- Muốn phân biệt với các exception hệ thống
- Cần xử lý lỗi theo cách riêng
public class UserService {
public void createUser(User user) throws BusinessException {
List<String> violations = validateUser(user);
if (!violations.isEmpty()) {
throw new ValidationException("User validation failed", violations);
}
if (userExists(user.getEmail())) {
throw new BusinessException(
"User already exists",
"USER_EXISTS"
);
}
// Tạo user
}
private List<String> validateUser(User user) {
List<String> violations = new ArrayList<>();
if (user.getName() == null || user.getName().trim().isEmpty()) {
violations.add("Name is required");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
violations.add("Invalid email format");
}
return violations;
}
}
4. Best Practices
4.1 Xử Lý Exception Đúng Cách
- Nguyên tắc cơ bản:
- Log exception đầy đủ thông tin
- Không nuốt (swallow) exception
- Xử lý exception ở mức phù hợp
- Đóng gói exception hệ thống
public class ExceptionHandlingBestPractices {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlingBestPractices.class);
public void demonstrateBestPractices() {
try {
// Thực hiện nghiệp vụ
processBusinessLogic();
} catch (BusinessException e) {
// Log error và xử lý nghiệp vụ
logger.error("Business error: {} - Code: {}",
e.getMessage(), e.getErrorCode());
// Thông báo cho người dùng
notifyUser(e.getMessage());
} catch (Exception e) {
// Log unexpected errors
logger.error("Unexpected error", e);
// Thông báo lỗi chung
notifyUser("An unexpected error occurred");
}
}
}
4.2 Exception Translation
- Mục đích:
- Chuyển đổi exception thấp cấp thành cao cấp
- Giữ nguyên thông tin gốc qua cause
- Cung cấp thông tin có ý nghĩa hơn
public class ExceptionTranslation {
public void translateExceptions() {
try {
// Database operation
performDatabaseOperation();
} catch (SQLException e) {
// Translate to custom exception
throw new DatabaseException("Database operation failed", e);
} catch (TimeoutException e) {
// Translate to custom exception
throw new ServiceException("Service timeout", e);
}
}
}
5. Anti-patterns và Cách Tránh
5.1 Anti-patterns
- Các lỗi thường gặp:
- Catch Exception quá rộng
- Bỏ qua exception (empty catch block)
- Throw exception chung chung
- Log và throw cùng một exception
public class ExceptionAntiPatterns {
// Anti-pattern 1: Catch Exception
public void catchAll() {
try {
// Some code
} catch (Exception e) {
// BAD: Catching all exceptions
}
}
// Anti-pattern 2: Empty Catch Block
public void emptyCatch() {
try {
// Some code
} catch (IOException e) {
// BAD: Empty catch block
}
}
// Anti-pattern 3: Throwing Raw Exception
public void throwRaw() throws Exception {
// BAD: Throwing raw Exception
throw new Exception("Something went wrong");
}
}
5.2 Best Solutions
- Nguyên tắc áp dụng:
- Catch exception cụ thể
- Xử lý hoặc chuyển tiếp exception
- Sử dụng custom exception có ý nghĩa
- Log exception ở một nơi duy nhất
public class ExceptionBestSolutions {
private static final Logger logger = LoggerFactory.getLogger(ExceptionBestSolutions.class);
// Solution 1: Specific Exception Handling
public void handleSpecific() {
try {
// Some code
} catch (IOException e) {
logger.error("IO error occurred", e);
throw new ServiceException("IO operation failed", e);
} catch (SQLException e) {
logger.error("Database error occurred", e);
throw new DatabaseException("Database operation failed", e);
}
}
// Solution 2: Proper Logging and Rethrowing
public void properHandling() {
try {
// Some code
} catch (IOException e) {
logger.error("Error occurred", e);
// Rethrow as custom exception
throw new ServiceException("Operation failed", e);
}
}
}