Là một Java developer, bạn nên có kiến thức tốt về Java exception và exception handling (xử lý ngoại lệ).
Hướng dẫn này cung cấp kiến thức cơ bản mà mọi lập trình viên phải có khi làm việc với chương trình Java. Để bắt đầu, hãy hiểu rõ Java Exception là gì.
Java Exception Là Gì
Một chương trình Java có thể gặp phải vấn đề dẫn đến chương trình bị chấm dứt đột ngột trong quá trình thực thi. Những vấn đề này gọi là exception (ngoại lệ).
Một lập trình viên giỏi nên nhận ra được các lỗi có thể xảy ra trong lúc chạy và cung cấp các đường đi thay thế cho chương trình trong trường hợp exception đó xảy ra. Thực hành này gọi là exception handling (xử lý ngoại lệ).
Bây giờ bạn có thể thắc mắc tại sao chúng ta cần exception handling. Tại sao không viết chương trình không ném exception?
Tại Sao Cần Exception Handling
Viết chương trình không ném exception không dễ như nghe. Hầu hết thời gian, những lỗi không thể tránh khỏi này nằm ngoài sự kiểm soát của lập trình viên.
Chương trình chấp nhận user input dễ gặp exception do input không hợp lệ user cung cấp. Cũng giống như đọc file bên ngoài coi chừng chúng đã bị di chuyển, đổi tên, hoặc xóa bởi nguồn bên ngoài mà lập trình viên không hay biết.
Trong những trường hợp đó, chương trình phải có thể xử lý exception một cách nhẹ nhàng (gracefully) mà không chấm dứt thực thi.
Phân Cấp Của Java Exception
Tất cả exception trong Java phải là con của class Exception, mà nó lại là con của class Throwable.
Hai subclass chính của class Exception là RuntimeException và IOException.
Exception Vs Error
Một class con khác của class Throwable là class Error. Tuy nhiên, error khác với exception.
Error chỉ ra vấn đề mà JVM có thể gặp trong lúc thực thi. Những vấn đề này thường nghiêm trọng và không thể khôi phục (irrecoverable). Rò rỉ bộ nhớ (memory leak) và vấn đề không tương thích thư viện (library incompatibility) là lý do thường gặp cho error trong chương trình.
StackOverflowError và OutOfMemoryError là hai ví dụ của Java Error.
Checked Và Unchecked Exception
Chúng ta có thể chia Java Exception thành hai loại chính: checked và unchecked exception.
Checked exception là exception cần được xử lý trong chương trình trước khi compile. Nếu exception này không được xử lý, chương trình sẽ không được compile bởi Java compiler. Do đó, chúng cũng gọi là compile-time exception. IOExceptions là ví dụ tốt của checked exception.
Unchecked exception là exception mà compiler bỏ qua khi compile chương trình. Cho dù chúng ta có xử lý exception này trong chương trình hay không không quan trọng khi chương trình được compile. Vì exception handling không được ép buộc trên các exception này, chương trình của chúng ta có thể gặp RuntimeExceptions dẫn đến chấm dứt chương trình.
Tất cả class extends class RuntimeException là unchecked exception. Hai ví dụ của class như vậy là NullPointerException và ArrayIndexOutOfBoundsException.
Phương Thức Thông Dùng Trong Exception Class
Chúng ta sẽ đi qua một vài phương thức thường dùng trong class Java Exception:
getMessage: trả về thông điệp chứa chi tiết về exception xảy ra.printStackTrace: trả về stack trace của exception xảy ra.toString: trả về tên class và thông điệp được trả về bởi phương thứcgetMessage.
Cách Xử Lý Exception
Hãy xem cách chúng ta có thể xử lý exception trong Java:
try-catch
Chúng ta có thể bắt (catch) exception và xử lý chúng đúng cách dùng block try-catch trong Java.
Trong cú pháp này, phần code dễ ném exception được đặt trong try block và catch block/blocks bắt exception/ném ra và xử lý chúng theo logic chúng ta cung cấp.
Cú pháp cơ bản của try-catch block như sau:
try {
// exception-prone code
} catch(Exception e) {
// error handling logic
}
Với cách này, chương trình không dừng thực thi khi exception được ném bởi chương trình, thay vào đó, nó được xử lý nhẹ nhàng.
Chúng ta sẽ xem cách xử lý IOExceptions được ném bởi class FileReader trong chương trình Java.
Ví dụ:
import java.io.FileReader;
public class TryCatchBlockExample {
public static void main(String[] args) {
try {
FileReader file = new FileReader("source.txt");
file.read();
} catch(Exception e) {
e.printStackTrace();
}
}
}
Ở đây, chúng ta dùng một catch block đơn để xử lý FileNotFoundException ném khi khởi tạo FileReader và IOException ném bởi phương thức read() của class FileReader.
Cả hai exception này đều là con của class Exception.
Chúng ta cũng có thể dùng nhiều catch statement để bắt các loại lỗi khác nhau được ném bởi code bên trong cùng một try statement. Cho ví dụ trước, chúng ta có thể dùng một catch block cho FileNotFoundException và một catch block khác cho IOException như đoạn code sau:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TryCatchBlockExample {
public static void main(String[] args) {
try {
FileReader file = new FileReader("source.txt");
file.read();
file.close();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
}
Nếu exception ném ra khớp với exception được xử lý bởi catch statement đầu tiên, nó sẽ được xử lý bởi logic bên trong catch statement đầu tiên.
Nếu exception không khớp, nó được truyền cho catch statement thứ hai. Nếu có hơn hai catch statement, quá trình này tiếp tục cho đến khi exception đến catch statement bắt được loại của nó.
Vì FileNotFoundException là subtype của IOException, dùng catch statement thứ 2 để bắt FileNotFoundException sẽ không hoạt động. Nó sẽ được xử lý bởi catch statement đầu tiên và không bao giờ đến statement thứ 2.
Lưu ý: Bắt buộc phải dùng ít nhất một catch statement với một try statement.
finally
Khi chúng ta dùng block try-catch để bắt exception trong chương trình, có trường hợp chúng ta muốn triển khai một số logic bất kể exception có bị bắt hay không. Trong trường hợp đó, chúng ta có thể dùng block try-catch-finally thay vì chỉ try-catch.
Sau đó, code bên trong statement finally được triển khai dù exception có xảy ra hay không. Statement finally phải luôn đặt ở cuối block try-catch-finally.
Ví dụ, khi dùng class FileReader đọc file, cần thiết phải đóng file mở ở cuối xử lý dù exception có xảy ra hay không. Để đảm bảo điều này, chúng ta có thể đặt code đóng file bên trong statement finally.
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TryCatchFinallyBlockExample {
public static void main(String[] args) {
FileReader file = null;
try {
file = new FileReader("source.txt");
file.read();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally {
file.close();
}
}
}
Tuy nhiên, nếu bạn thử compile code trên, code sẽ không compile do IOException không được xử lý. Điều này là vì phương thức close() của class FileReader cũng có thể ném IOExceptions. Vì vậy, chúng ta phải đặt phần này trong một try block khác như sau:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TryCatchFinallyBlockExample {
public static void main(String[] args) {
FileReader file = null;
try {
file = new FileReader("source.txt");
file.read();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
file.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
throws
Xử lý lỗi dùng từ khóa throws trong Java rất đơn giản. Thực tế, trong cách này, bạn không thực sự xử lý exception tại chỗ nó xảy ra. Thay vào đó, chúng ta ném exception ra khỏi phương thức hiện tại đến phương thức gọi phương thức hiện tại. Sau đó, việc xử lý lỗi trở thành trách nhiệm của phương thức bên ngoài.
Để ném exception ra khỏi một phương thức, bạn chỉ cần khai báo phương thức đó có thể ném exception được xem xét. Hãy xem cách xử lý IOExceptions ném bởi class FileReader dùng cách này.
Ví dụ:
import java.io.FileReader;
import java.io.IOException;
public class ThrowsExample {
public void readFile() throws IOException {
FileReader file = new FileReader("source.txt");
file.read();
file.close();
}
}
throw
Khác với các cách khác trong danh sách này, từ khóa throw không dùng để xử lý lỗi. Nhưng vì hầu hết mọi người nhầm lẫn throw với throws, chúng tôi nghĩ tốt nhất nên bàn ở đây.
Từ khóa throw dùng để ném rõ ràng (explicitly invoke) một exception. Chúng ta có thể ném một exception mới khởi tạo hoặc một exception đã bị bắt bên trong phương thức.
public class ThrowExample {
public void invalidate(int amount) throws Exception {
if (amount < 500) {
throw new Exception("Amount not sufficient");
}
}
}
User-Defined Exceptions (Exception Do Người Dùng Định Nghĩa)
Ngoài dùng built-in Java exception, bạn có thể định nghĩa exception của riêng bạn. Bạn có thể định nghĩa chúng như checked hoặc unchecked exception. Để tạo checked exception mới, exception mới của bạn nên extend class Exception.
Để tạo unchecked exception, extend class RuntimeException.
Trong ví dụ code sau, chúng ta đã tạo một user-defined checked exception:
public class InvalidLengthException extends Exception {
private int length;
private String message;
public InvalidLengthException(int length, String message) {
this.length = length;
this.message = message;
}
public int getAmount() {
return this.length;
}
public String getMessage() {
return this.message;
}
}
Bây giờ chúng ta có thể dùng exception trên trong logic chương trình như sau:
public class InputChecker {
private int minLength;
private int maxLength;
public InputChecker(int minLength, int maxLength) {
this.minLength = minLength;
this.maxLength = maxLength;
}
public void checkStringLength(String strInput) throws InvalidLengthException {
int strLength = strInput.length();
if (strLength < minLength) {
throw new InvalidLengthException(strLength, "Input should have minimum " + minLength + " characters");
} else if (strLength > maxLength) {
throw new InvalidLengthException(strLength, "Input should have maximum " + maxLength + " character");
}
}
}
Nếu chúng ta kiểm tra độ dài string dùng class InputChecker, nó sẽ ném InvalidLengthException nếu độ dài string dưới độ dài tối thiểu hoặc trên độ dài tối đa.
public class Main {
public static void main(String[] args) {
InputChecker ic = new InputChecker(2, 7);
try {
ic.checkStringLength("longer than the maximum length");
} catch(InvalidLengthException e) {
e.printStackTrace();
}
}
}
Khi chạy đoạn code trên, nó sẽ ném InvalidLengthException và chúng ta sẽ nhận được output sau:
InvalidLengthException: Input should have maximum 7 character
at InputChecker.checkStringLength(InputChecker.java:17)
at Main.main(Main.java:6)
Kết Luận
Trong hướng dẫn này, chúng ta đã cho bạn một giới thiệu nhanh và súc tích về Java Exception. Chúng ta hy vọng bạn hiện có hiểu tốt về exception là gì và cách xử lý chúng trong chương trình Java của bạn.