Exception là gì?
Exception (ngoại lệ) là sự kiện bất thường xảy ra trong quá trình thực thi chương trình, làm gián đoạn luồng code bình thường. Java cung cấp cơ chế mạnh mẽ để xử lý exceptions một cách có cấu trúc.
Phân loại Exception trong Java
1. Checked Exception
Exceptions được kiểm tra tại compile-time, bắt buộc phải xử lý:
// Phải xử lý IOException
public void readFile(String path) throws IOException {
FileReader file = new FileReader(path);
BufferedReader reader = new BufferedReader(file);
// ...
}
Ví dụ: IOException, SQLException, ClassNotFoundException
2. Unchecked Exception (Runtime Exception)
Exceptions xảy ra tại runtime, không bắt buộc xử lý:
int[] arr = new int[5];
arr[10] = 50; // ArrayIndexOutOfBoundsException
Ví dụ: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException
3. Error
Lỗi nghiêm trọng, thường không nên xử lý:
OutOfMemoryErrorStackOverflowError
Cú pháp Try-Catch-Finally
Cơ bản
try {
// Code có thể gây exception
int result = 10 / 0;
} catch (ArithmeticException e) {
// Xử lý lỗi
System.out.println("Không thể chia cho 0: " + e.getMessage());
} finally {
// Luôn được thực thi
System.out.println("Cleanup code");
}
Multiple Catch Blocks
try {
String str = null;
System.out.println(str.length());
int result = 10 / 0;
} catch (NullPointerException e) {
System.out.println("Null pointer: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Arithmetic error: " + e.getMessage());
} catch (Exception e) {
// Catch tổng quát - đặt cuối cùng
System.out.println("General error: " + e.getMessage());
}
Multi-catch (Java 7+)
try {
// Code
} catch (IOException | SQLException e) {
System.out.println("IO hoặc SQL error: " + e.getMessage());
}
Try-with-Resources (Java 7+)
Tự động đóng resources, không cần finally:
// Cách cũ
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Cách mới (Java 7+)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
Lợi ích:
- Code ngắn gọn hơn
- Tự động close() resources
- Không bị resource leak
Multiple Resources
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
// Sử dụng cả 2 streams
} catch (IOException e) {
e.printStackTrace();
}
Throws và Throw
throws - Khai báo exception
public void readFile(String path) throws IOException, FileNotFoundException {
FileReader file = new FileReader(path);
// ...
}
throw - Ném exception
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Tuổi không thể âm!");
}
this.age = age;
}
Custom Exception
Tạo Exception riêng
public class InsufficientBalanceException extends Exception {
private double amount;
public InsufficientBalanceException(double amount) {
super("Số dư không đủ: thiếu " + amount);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
Sử dụng Custom Exception
public class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(amount - balance);
}
balance -= amount;
}
}
// Sử dụng
try {
account.withdraw(1000);
} catch (InsufficientBalanceException e) {
System.out.println(e.getMessage());
System.out.println("Thiếu: " + e.getAmount());
}
Exception Propagation
Exception được lan truyền lên call stack:
public class ExceptionPropagation {
public void method1() {
method2();
}
public void method2() {
method3();
}
public void method3() {
throw new RuntimeException("Lỗi ở method3");
}
public static void main(String[] args) {
try {
new ExceptionPropagation().method1();
} catch (Exception e) {
e.printStackTrace();
// Stack trace sẽ hiển thị: method3 -> method2 -> method1 -> main
}
}
}
Best Practices
1. Đừng nuốt exception
// ❌ SỬA: Không làm thế này
try {
riskyOperation();
} catch (Exception e) {
// Không làm gì - exception bị "nuốt"
}
// ✅ TỐT: Ít nhất log lại
try {
riskyOperation();
} catch (Exception e) {
logger.error("Error during risky operation", e);
}
2. Catch exception cụ thể
// ❌ SỬA
try {
// code
} catch (Exception e) {
// Quá chung chung
}
// ✅ TỐT
try {
// code
} catch (IOException e) {
// Xử lý IO error cụ thể
} catch (SQLException e) {
// Xử lý SQL error cụ thể
}
3. Đóng resources đúng cách
// ❌ SỬA: Dùng finally thủ công
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
} finally {
if (fis != null) fis.close();
}
// ✅ TỐT: Dùng try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
// code
}
4. Exception message rõ ràng
// ❌ SỬA
throw new IllegalArgumentException("Invalid");
// ✅ TỐT
throw new IllegalArgumentException(
"Invalid age value: " + age + ". Age must be between 0 and 150"
);
5. Không dùng exception để control flow
// ❌ SỬA: Dùng exception như control flow
try {
while (true) {
array[i++] = getValue();
}
} catch (ArrayIndexOutOfBoundsException e) {
// Xong rồi
}
// ✅ TỐT: Dùng điều kiện bình thường
for (int i = 0; i < array.length; i++) {
array[i] = getValue();
}
6. Log với context đầy đủ
try {
processPayment(userId, amount);
} catch (PaymentException e) {
logger.error(
"Payment failed for user: {} with amount: {}",
userId,
amount,
e
);
throw e;
}
Ví dụ thực tế
1. Service Layer với Exception Handling
@Service
public class UserService {
private UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
public User createUser(UserDTO dto) {
try {
validateUser(dto);
User user = new User(dto.getName(), dto.getEmail());
return userRepository.save(user);
} catch (ValidationException e) {
logger.error("Validation failed for user: {}", dto, e);
throw new BadRequestException(e.getMessage());
} catch (DataAccessException e) {
logger.error("Database error creating user: {}", dto, e);
throw new ServiceException("Unable to create user", e);
}
}
}
2. REST Controller với Exception Handling
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
try {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
} catch (UserNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
e.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
3. Retry với Exception
public <T> T executeWithRetry(Supplier<T> operation, int maxRetries) {
int attempts = 0;
Exception lastException = null;
while (attempts < maxRetries) {
try {
return operation.get();
} catch (Exception e) {
lastException = e;
attempts++;
logger.warn("Attempt {} failed, retrying...", attempts);
if (attempts < maxRetries) {
try {
Thread.sleep(1000 * attempts); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
}
throw new RuntimeException("Failed after " + maxRetries + " attempts", lastException);
}
Kết luận
Exception handling là kỹ năng quan trọng trong Java:
- Hiểu rõ checked vs unchecked exceptions
- Sử dụng try-with-resources cho resource management
- Tạo custom exceptions khi cần
- Follow best practices để code dễ maintain
- Log exceptions đầy đủ context
Xử lý exception đúng cách giúp ứng dụng robust và dễ debug hơn!