- Sản phẩm & Dịch vụSản phẩm & Dịch vụ
- Giải phápGiải pháp
- Bảng giáBảng giá
- Công tyCông ty
- Tài liệuTài liệu
vi
vi
Chuyên mục với các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.
Web Developer
I’m passionate about web development and sharing my insights through articles, with over 8 years of experience. I hope these sharings inspire you and help build a strong web development community. @#@ Tôi đam mê phát triển web và chia sẻ những hiểu biết của mình thông qua các bài viết, với hơn 8 năm kinh nghiệm. Tôi hy vọng những chia sẻ này sẽ truyền cảm hứng cho các bạn và giúp xây dựng một cộng đồng phát triển web mạnh mẽ.
Thảo luận (0)
Đăng nhập để thảo luận
Một exception (ngoại lệ) là một lỗi có thể xảy ra trong quá trình thực thi chương trình và làm gián đoạn luồng xử lý thông thường. Java cung cấp một cơ chế mạnh mẽ và có tính hướng đối tượng để xử lý ngoại lệ, được gọi là Java Exception Handling.

Exception trong Java có thể phát sinh từ nhiều tình huống khác nhau, ví dụ như người dùng nhập sai dữ liệu, lỗi phần cứng, mất kết nối mạng, hoặc database server bị lỗi. Phần code quy định cách xử lý các tình huống exception cụ thể này được gọi là exception handling.
Khi một lỗi xảy ra trong quá trình thực thi một câu lệnh, Java sẽ tạo ra một đối tượng exception. Nó chứa rất nhiều thông tin dùng để debug lỗi như thứ bậc của method, số dòng nơi exception xảy ra và loại exception.
Nếu một exception xảy ra trong một method, quá trình tạo đối tượng exception và giao nó cho môi trường runtime được gọi là “ném ngoại lệ” (throwing the exception). Lúc này, lồng thực thi của chương trình sẽ bị tạm dừng và Java Runtime Environment (JRE) sẽ cố gắng tìm một trình xử lý ngoại lệ (exception handler) phù hợp
Exception Handler là một khối code có khả năng xử lý đối tượng exception.
Do đó, nếu call stack (một cấu trúc dữ liệu theo dõi thứ tự các hàm được gọi trong chương trình) của các method là A() -> B() -> C() và một exception phát sinh trong method C(), thì việc tìm kiếm handler phù hợp sẽ di chuyển từ C() -> B() -> A().
Khi tìm thấy một exception handler phù hợp, đối tượng exception sẽ được chuyển đến handler đó để xử lý. Ta nói rằng handler này đã “bắt ngoại lệ” (catching the exception). Nếu không tìm thấy handler nào phù hợp, chương trình sẽ kết thúc và in thông tin về exception ra console.
Framework xử lý exception của Java chỉ được dùng để xử lý các lỗi runtime. Các lỗi lúc biên dịch (compile-time) phải được lập trình viên sửa trực tiếp trong mã nguồn, nếu không chương trình sẽ không thể thực thi.
Java cung cấp các từ khóa cụ thể cho mục đích xử lý exception.
throw được sử dụng để đưa exception cho runtime xử lý.throws trong chữ ký của method để cho chương trình gọi nó biết các exception có thể được tạo ra từ method này. Method gọi (caller) có thể xử lý các exception này hoặc chuyển tiếp chúng lên caller của nó bằng từ khóa throws. Ta có thể khai báo nhiều exception trong mệnh đề throws và nó cũng có thể được sử dụng với method main.try-catch để nhận lấy và xử lý exception trong code. try là điểm bắt đầu của khối và catch nằm ở cuối khối try để xử lý các exception. Ta có thể có nhiều khối catch với một khối try. Khối try-catch cũng có thể được lồng vào nhau. Khối catch yêu cầu một tham số phải có kiểu là Exception.finally không bắt buộc phải có và chỉ có thể được sử dụng với khối try-catch. Vì exception làm tạm dừng quá trình thực thi, có thể có một số tài nguyên đang mở mà không được đóng lại. Trường hợp này ta có thể sử dụng khối finally để giải quyết các tafin nguyên đó. Nó luôn được thực thi, bất kể exception có xảy ra hay không.package com.journaldev.exceptions;import java.io.FileNotFoundException; import java.io.IOException;
public class ExceptionHandling {
public static void main(String[] args) throws FileNotFoundException, IOException { try { testException(-5); testException(-10); } catch(FileNotFoundException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } finally { System.out.println("Releasing resources"); } testException(15); } public static void testException(int i) throws FileNotFoundException, IOException { if (i < 0) { FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i); throw myException; } else if (i > 10) { throw new IOException("Only supported for index 0 to 10"); } }}
processFile() tạo exception bằng từ khóa throw. Chữ ký của method sử dụng từ khóa throws để cho bên gọi biết các loại exception mà nó có thể ném ra.main() xử lý các exception bằng khối try-catch. Khi không xử lý chúng, nó sẽ lan truyền các lỗi đến runtime bằng mệnh đề throws.testException(-10) không bao giờ được thực thi do có exception trước đó, và sau đó khối finally sẽ được thực thi.printStackTrace() là một trong những method hữu ích nhất trong class Exception cho mục đích debug.
Đoạn code trên sẽ cho ra output sau:
java.io.FileNotFoundException: Negative Integer -5 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10) Releasing resources Exception in thread "main" java.io.IOException: Only supported for index 0 to 10 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
Một số điểm quan trọng cần lưu ý:
catch hoặc finally mà không có câu lệnh try.try phải có hoặc khối catch hoặc khối finally, và cũng có thể có cả hai.try-catch-finally.catch với một câu lệnh try.try-catch có thể được lồng vào nhau tương tự như câu lệnh if-else.finally với một câu lệnh try.Như đã đề cập trước đó, khi một lỗi xuất hiện, một đối tượng exception sẽ được tạo. Các exception trong Java được tổ chức theo hệ thống phân cấp và sử dụng tính kế thừa để phân loại chúng.
Throwable là class cha của hệ thống phân cấp các exception trong Java. Nó có hai đối tượng con: Error và Exception. Các Exception lại được chia thành Checked Exception và Runtime Exception.
FileNotFoundException. Ta nên nhận lấy exception này, cung cấp một thông báo hữu ích cho người dùng và ghi log lại để tiện cho việc debug. Exception là class cha của tất cả các Checked Exception. Nếu ta tạo ra một Checked Exception, ta phải xử lý nó trong cùng một method hoặc phải chuyển tiếp nó cho method gọi bằng từ khóa throws.ArrayIndexOutOfBoundsException tại thời điểm runtime. RuntimeException là class cha của tất cả các Runtime Exception. Nếu ta throw bất kỳ RuntimeException nào trong một method, không bắt buộc phải khai báo chúng trong mệnh đề throws của chữ ký method. Các Runtime Exception có thể được tránh bằng cách lập trình cẩn thận hơn.
Throwable và tất cả các class con của nó không cung cấp bất kỳ method đặc thù nào. Tất cả các method đều được định nghĩa trong class cơ sở Throwable. Các class Exception được tạo ra để xác định các loại kịch bản lỗi khác nhau để ta có thể dễ dàng xác định nguyên nhân gốc rễ và xử lý Exception tùy theo loại của nó. Class Throwable triển khai interface Serializable để đảm bảo khả năng tương thích với nhau.
Một số method hữu ích của class Throwable là:
public String getMessage(): Method này trả về String thông báo của Throwable và thông báo này có thể được cung cấp khi tạo exception thông qua constructor của nó.public String getLocalizedMessage(): Method này được cung cấp để các class con có thể ghi đè nó nhằm cung cấp một thông báo theo ngôn ngữ cụ thể cho chương trình gọi. Cách triển khai của method này trong class Throwable chỉ đơn giản là sử dụng method getMessage() để trả về thông báo exception.public synchronized Throwable getCause(): Method này trả về nguyên nhân của exception hoặc null nếu không rõ nguyên nhân.public String toString(): Method này trả về thông tin về Throwable dưới định dạng String, chứa tên của class Throwable và thông báo đã được địa phương hóa.public void printStackTrace(): Method này in thông tin stack trace (danh sách các hàm đã gọi đến khi xảy ra lỗi) ra standard error stream (luồng dữ liệu riêng biệt để gửi thông báo lỗi). Method này được nạp chồng và ta có thể truyền PrintStream hoặc PrintWriter làm đối số để ghi thông tin stack trace ra file hoặc stream.Khi xử lý nhiều exception trong một khối try, bạn sẽ nhận thấy rằng khối catch hay chứa các đoạn code lặp đi lặp lại để ghi log lỗi. Trong Java 7, một trong những tính năng mới là ta có thể xử lý nhiều exception trong một khối catch duy nhất.
Dưới đây là một ví dụ về khối catch với tính năng này:
catch (IOException | SQLException ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
}
Sẽ có một số ràng buộc, ví dụ như việc đối tượng exception là final và ta không thể sửa đổi nó bên trong khối catch.
Trong hầu hết trường hợp, ta sử dụng khối finally chỉ để đóng các tài nguyên. Đôi khi ta quên đóng chúng và gặp các exception runtime khi tài nguyên bị cạn kiệt. Những exception này rất khó để debug, và ta có thể phải xem xét mọi nơi đã sử dụng tài nguyên đó để đảm bảo rằng ta đã đóng nó.
Java 7 thêm một tính năng mới là try-with-resources. Ta có thể dùng nó để tạo một tài nguyên ngay trong câu lệnh try và sử dụng nó bên trong khối try-catch. Khi chương trình thoát ra khỏi khối try, môi trường runtime sẽ tự động đóng các tài nguyên này.
Dưới đây là một ví dụ về khối try-catch với cải tiến này:
try (MyResource mr = new MyResource()) {
System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
e.printStackTrace();
}
Java cung cấp rất nhiều class exception để chúng ta sử dụng, nhưng đôi khi ta có thể cần tạo các class tùy chỉnh của riêng mình. Ví dụ, ta có thể dùng nó để thông báo cho caller về một loại exception cụ thể với thông báo thích hợp. Ta cũng có thể có các trường tùy chỉnh để theo dõi mã lỗi.
Giả sử ta viết một method chỉ để xử lý các file văn bản. Ta có thể cung cấp cho caller mã lỗi phù hợp khi một loại file khác nào đó cung cấp ở đầu vào.
Đầu tiên, tạo file MyException.java:
package com.journaldev.exceptions;public class MyException extends Exception {
private static final long serialVersionUID = 4664456874499611218L; private String errorCode = "Unknown_Exception"; public MyException(String message, String errorCode) { super(message); this.errorCode=errorCode; } public String getErrorCode() { return this.errorCode; }}
Sau đó, tạo CustomExceptionExample.java:
package com.journaldev.exceptions;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream;
public class CustomExceptionExample {
public static void main(String[] args) throws MyException { try { processFile("file.txt"); } catch (MyException e) { processErrorCodes(e); } } private static void processErrorCodes(MyException e) throws MyException { switch (e.getErrorCode()) { case "BAD_FILE_TYPE": System.out.println("Bad File Type, notify user"); throw e; case "FILE_NOT_FOUND_EXCEPTION": System.out.println("File Not Found, notify user"); throw e; case "FILE_CLOSE_EXCEPTION": System.out.println("File Close failed, just log it."); break; default: System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage()); e.printStackTrace(); } } private static void processFile(String file) throws MyException { InputStream fis = null; try { fis = new FileInputStream(file); } catch (FileNotFoundException e) { throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION"); } finally { try { if (fis != null) fis.close(); } catch (IOException e) { throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION"); } } }}
Ta có thể có một method riêng để xử lý các loại mã lỗi khác nhau mà ta nhận được từ các method khác nhau. Một số trong số chúng sẽ được consume (được xử lý và không còn tồn tại) vì ta có thể không muốn thông báo cho người dùng về điều đó. Hoặc một số khác ta sẽ throw lại để thông báo cho người dùng về vấn đề.
Ở đây ta đang kế thừa từ Exception để bất cứ khi nào exception này được tạo ra, nó phải được xử lý trong method hoặc được trả về cho chương trình gọi. Nếu kế thừa từ RuntimeException, ta không cần phải khai báo nó trong mệnh đề throws.
Đây là một quyết định có chủ đích khi thiết kế chương trình. Sử dụng Checked Exception có lợi thế là giúp các lập trình viên hiểu được những exception nào họ có thể gặp phải và có hành động phù hợp để xử lý chúng.
IOException với các class con khác như FileNotFoundException, SocketException, v.v. Ta nên luôn throw và catch các class exception cụ thể để bên gọi có thể dễ dàng biết được nguyên nhân gốc rễ của exception và xử lý chúng một cách thích hợp. Điều này giúp việc debug dễ hơn và giúp các ứng dụng client xử lý exception phù hợp.throw exception càng sớm càng tốt. Hãy xem xét method processFile() ở trên. Nếu ta truyền tham số null vào, ta sẽ nhận được exception sau đây:Exception in thread "main" java.lang.NullPointerException at java.io.FileInputStream.<init>(FileInputStream.java:134) at java.io.FileInputStream.<init>(FileInputStream.java:97) at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
Khi debug, ta sẽ phải xem xét kỹ stack trace để xác định vị trí thật sự đã gây ra exception. Ta có thể thay đổi logic để kiểm tra các exception này sớm hơn như sau:
private static void processFile(String file) throws MyException {
if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");
// ... further processing
}
Khi đó stack trace sẽ chỉ ra nơi exception xảy ra cùng với một thông báo rõ ràng:
com.journaldev.exceptions.MyException: File name can't be null at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
finally hoặc sử dụng cải tiến try-with-resources của Java 7 để cho Java runtime tự động đóng chúng.catch trống chỉ consume exception mà không cung cấp bất kỳ chi tiết có ý nghĩa nào cho việc debug.catch duy nhất. Cách tiếp cận này sẽ giảm độ dài code và làm cho nó trông gọn gàng hơn.throw và catch nhiều exception, ta có thể tạo một exception tùy chỉnh với mã lỗi riêng để chương trình gọi có thể xử lý các mã lỗi này. Ta cũng nên tạo một method tiện ích để xử lý các mã lỗi khác nhau và sử dụng chúng.Exception để ngầm định đây là một class exception. Ngoài ra, hãy đóng gói chúng giống như cách được thực hiện trong Java Development Kit (JDK). Ví dụ, IOException là exception cơ sở cho tất cả các tiến trình IO.@throws của Javadoc để chỉ định rõ ràng các exception được throw bởi method. Thông tin này sẽ có ích khi bạn cung cấp một interface cho các ứng dụng khác để sử dụng.Trong bài viết này, bạn đã tìm hiểu về xử lý exception trong Java. Bạn đã học về throw và catch, cũng như các khối try (và try-with-resources), catch, và finally.
Các kiến thức này sẽ giúp bạn viết các chương trình Java có khả năng xử lý lỗi phát sinh một cách thông minh và linh hoạt hơn, đảm bảo ứng dụng vận hành một cách ổn định và đáng tin cậy, ngay cả khi gặp phải lỗi không mong muốn.