Xử lý ngoại lệ trong Java (Exception Handling)

1. Ngoại lệ (Exception) là gì?

Exception là một sự kiện xảy ra trong quá trình thực thi một chương trình Java, nó làm phá vỡ cái flow (luồng xử lý) bình thường của một chương trình, thậm chí chết chương trình.

Một ngoại lệ có thể xảy ra với nhiều lý do khác nhau, nó nằm ngoài dự tính của chương trình. Một vài ngoại lệ xảy ra bởi lỗi của người dùng, một số khác bởi lỗi của lập trình viên và số khác nữa đến từ lỗi của nguồn dữ liệu vật lý. Chẳng hạn như:

  • Người dùng nhập dữ liệu không hợp lệ.
  • Truy cập ngoài chỉ số mảng.
  • Một file cần được mở nhưng không thể tìm thấy.
  • Kết nối mạng bị ngắt trong quá trình thực hiện giao tiếp hoặc JVM hết bộ nhớ.
  • ….

Ví dụ chương trình chia 2 số. Nếu ta cho mẫu số = 0 thì phát sinh lỗi và đó được coi là 1 ngoại lệ.

public class ExceptionExample1 {
 
    public static void main(String[] args) {
 
        int zero = 0;
 
        int average = 10 / zero;
 
        System.out.println("Average = " + average);
 
    }
 
}

Khi thực thi chương trên, sẽ nhận được thông báo lỗi như sau:

Exception in thread "main" java.lang.ArithmeticException: / by zero

2. Hệ thống cấp bậc của các lớp ngoại lệ trong Java

Đây là mô hình sơ đồ phân cấp của Exception trong java.

  • Class ở mức cao nhất là Throwable
  • Hai class con trực tiếp là Error và Exception.

Trong nhánh Exception có một nhánh con RuntimeException là các ngoại lệ sẽ không được java kiểm tra trong thời điểm biên dịch.Ý nghĩa của được kiểm tra và không được kiểm tra tại thời điểm biên dịch sẽ được minh họa trong các ví dụ phần sau.

2.1. Exception

Trong Java có 2 loại exception: checked và unchecked. Tất cả các checked exception được kế thừa từ lớp Exception ngoại trừ lớp RuntimeException. RuntimeException là lớp cơ sở của tất cả các lớp unchecked exception. Đó cũng là dấu hiệu để nhận biết đâu là checked exception và đâu là unchecked exception.

Điểm khác biệt giữa các lớp checked và unchecked expcetion chính là thời điểm xác định được expcetion có thể xảy ra.

Checked exceptions

Là loại exception xảy ra trong lúc compile time, nó cũng có thể được gọi là compile time exceptions. Loại exception này không thể bỏ qua được trong quá trình compile, bắt buộc ta phải handle nó.

Các lớp extends từ lớp Throwable ngoại trừ RuntimeException và Error được gọi là checked exception.

Ví dụ: IOException, FileNotFoundException, NoSuchFieldException, ….

Ví dụ chương trình sau đọc file sử dụng java.io.FileReader lớp này ném ra ngoại lệ FileNotFoundException. Trình biên dịch thông báo lỗi như sau:

UnChecked exceptions

Là loại exception xảy ra tại thời điểm thực thi chương trình, nó cũng có thể gọi là runtime exceptions đó là programming bugs, lỗi logic của chương trình… Loại exception này được bỏ qua trong quá trình compile, không bắt buộc ta phải handle nó.

Các lớp extends từ RuntimeException được gọi là unchecked exception.

Ví dụ: NullPointerException, NumberFormatException, ArrayIndexOutOfBoundsException, DivideByZeroException, …

Ví dụ một biến có giá trị null, thực hiện bất kỳ hoạt động nào bởi biến đó sẽ xảy ra ngoại lệ NullPointerException.

2.2. Error

Error là những vấn đề nghiêm trọng liên quan đến môi trường thực thi của ứng dụng hoặc hệ thống mà lập trình viên không thể kiểm soát. Nó thường làm chết chương trình.

Lớp Error định nghĩa các ngoại lệ mà không thể bắt (catch) từ chương trình.

Ví dụ: OutOfMemoryError, VirtualMachineError, and StackOverflowError, …

Ví dụ chương trình đệ quy vô tận:

3. Các kịch bản phổ biến nơi ngoại lệ có thể xảy ra

3.1. ArithmeticException

Nếu chúng ta chia bất kỳ số nào cho số 0, xảy ra ngoại lệ ArithmeticException.

Ví dụ:

int a = 10 / 0; // ArithmeticException

3.2. NullPointerException

Nếu một bất kỳ biến nào có giá trị null, thực hiện bất kỳ hoạt động nào bởi biến đó sẽ xảy ra ngoại lệ NullPointerException.

Ví dụ:

String obj = null;
System.out.println(obj .length()); // NullPointerException

3.3. NumberFormatException

Một biến String có giá trị là các ký tự, chuyển đổi biến này thành số sẽ xảy ra NumberFormatException.

String str = "abc"; 
int num = Integer.parseInt(str); // NumberFormatException 

3.4. ArrayIndexOutOfBoundsException

Nếu bạn chèn bất kỳ giá trị nào vào index sai, sẽ xảy ra ngoại lệ ArrayIndexOutOfBoundsException.

int arr[] = new int[5];
arr[5] = 50; // ArrayIndexOutOfBoundsException

3.5. ClassCastException

Nếu không thể chuyển kiểu object này sang kiểu object khác, sẽ xảy ra ngoại lệ ClassCastException.

Object dog = new Dog();
Rectangle rect = (Rectangle) dog;

4. Xử lý ngoại lệ (Exception Handling) trong java

4.1. Xử lý ngoại lệ là gì?

Xử lý ngoại lệ (Exception Handling) trong java là một cơ chế xử lý các lỗi runtime để có thể duy trì luồng bình thường của ứng dụng.

Quá trình xử lý exception được gọi là catch exception, nếu Runtime System không xử lý được ngoại lệ thì chương trình sẽ kết thúc.

4.2. JVM xử lý Exceptions thế nào

Khi một lỗi xảy ra trên một method, method đó sẽ tạo ra một object và đưa nó vào Runtime System. Object đó được gọi là Exception Object, nó chứa tất cả các thông tin về lỗi và trạng thái của chương trình khi xảy ra lỗi.

Sau đó, Runtime System sẽ xử lý sẽ tìm cách xử lý ngoại lệ phù hợp được sử dụng tại method ấy. Nếu không có thì JVM tiếp tục tìm xử lý ngoại lệ phù hợp ở các method trên (là method gọi lớp hiện tại). Nếu không có method nào có xử lý ngoại lệ phù hợp thì Thread mà đang thực hiện chuỗi method xảy ra ngoại lệ bị ngắt. Nếu thread ấy là thread main thì chết chương trình.

4.3. Cú pháp xử lý ngoại lệ trong Java

Khối lệnh try trong java được sử dụng để chứa một đoạn code có thế xảy ra một ngoại lệ. Nó phải được khai báo trong phương thức.

Sau một khối lệnh try bạn phải khai báo khối lệnh catch hoặc finally hoặc cả hai.

4.3.1. Cú pháp của khối lệnh try-catch

try {
    // code có thể ném ra ngoại lệ
} catch(Exception_class_Name ex) {
    // code xử lý ngoại lệ
}

4.3.2. Cú pháp của khối lệnh try-finally

try {
// code có thể ném ra ngoại lệ
} finally {
// code trong khối này luôn được thực thi
}

4.3.3. Cú pháp của khối lệnh try-catch-finally

try {
    // code có thể ném ra ngoại lệ
} catch(Exception_class_Name_1 ex) {
    // code xử lý ngoại lệ 1
} catch(Exception_class_Name_2 ex) {
    // code xử lý ngoại lệ 2
} catch(Exception_class_Name_n ex) {
    // code xử lý ngoại lệ n
} finally {
    // code trong khối này luôn được thực thi
}

4.4. Các phương thức của lớp Exception

STTPhương thức và Miêu tả
1public String getMessage()
Trả về một message cụ thể về exception đã xảy ra. Message này được khởi tạo bởi phương thức contructor của Throwable
2public Throwable getCause()
Trả về nguyên nhân xảy ra exception biểu diễn bởi đối tượng Throwable
3public String toString()
Trả về tên của lớp và kết hợp với kết quả từ phương thức getMessage()
4public void printStackTrace()
In ra kết quả của phương thức toString cùng với stack trace đến System.err
5public StackTraceElement [] getStackTrace()
Trả về một mảng chứa mỗi phần tử trên stack trace. Phần tử tại chỉ mục 0 biểu diễn phần trên cùng của Call Stack, và phần tử cuối cùng trong mảng biểu diễn phương thức tại dưới cùng của Call Stack
6public Throwable fillInStackTrace()
Fills the stack trace of this Throwable object with the current stack trace, adding to any previous information in the stack trace.

4.5. Các ví dụ minh họa việc xử lý ngoại lệ

4.5.1. Ví dụ xử lý ngoại lệ thực hiện phép chia cho số 0

Khi chưa xử lý ngoại lệ

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        int zero = 0;
        int average = 10 / zero;
        System.out.println("Average = " + average);
        System.out.println("Finished!");
    }
 
}

Thực thi chương trình trên:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.maixuanvietr.exception.ExceptionExample1.main(ExceptionExample1.java:7)

Xử lý ngoại lệ ArithmeticException cho chương trình trên

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        try {
            int zero = 0;
            int average = 10 / zero;
            System.out.println("Average = " + average);
        } catch (ArithmeticException ex) {
            System.out.println(ex);
        }
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

java.lang.ArithmeticException: / by zero
Finished!

4.5.2. Ví dụ về khối lệnh try lồng nhau

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        try {
            try {
                int zero = 0;
                int average = 10 / zero;
                System.out.println("Average = " + average);
            } catch (ArithmeticException ex) {
                System.out.println(ex);
            }
 
            System.out.println("Continue...");
            int arr[] = new int[5];
            arr[5] = 4;
            System.out.println("arr[5] = " + arr[5]);
 
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println(ex);
        }
 
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

java.lang.ArithmeticException: / by zero
Continue...
java.lang.ArrayIndexOutOfBoundsException: 5
Finished!

4.5.3. Ví dụ sau sử dụng nhiều khối lệnh catch

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        try {
            int arr[] = new int[5];
            arr[5] = 4;
            System.out.println("arr[5] = " + arr[5]);
             
            int zero = 0;
            int average = 10 / zero;
            System.out.println("Average = " + average);
             
            String obj = null;
            System.out.println(obj.length());
        } catch (NullPointerException ex) {
            System.out.println(ex);
        } catch (ArithmeticException ex) {
            System.out.println(ex);
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println(ex);
        }
 
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

java.lang.ArrayIndexOutOfBoundsException: 5
Finished!

4.5.4. Ví dụ sử dụng khối lệnh finally nơi ngoại lệ không xảy ra

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        try {
            int res = 10/2;
            System.out.println("10/2 = " + res);
        } finally {
            System.out.println("Khối lệnh finally luôn được thực thi");
        }
 
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

10/2 = 5
Khối lệnh finally luôn được thực thi
Finished!

4.5.5. Ví dụ sử dụng khối lệnh finally nơi ngoại lệ xảy ra nhưng không xử lý

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        try {
            int arr[] = new int[5];
            arr[5] = 4;
            System.out.println("arr[5] = " + arr[5]);
        } catch (NullPointerException ex) {
            System.out.println(ex);
        } finally {
            System.out.println("Khối lệnh finally luôn được thực thi");
        }
 
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

Khối lệnh finally luôn được thực thiException in thread "main"
java.lang.ArrayIndexOutOfBoundsException: 5
    at com.maixuanvietr.exception.ExceptionExample1.main(ExceptionExample1.java:8)

4.5.6. Ví dụ sử dụng khối lệnh finally nơi ngoại lệ xảy ra và được xử lý

 
    public static void main(String[] args) {
        try {
            int arr[] = new int[5];
            arr[5] = 4;
            System.out.println("arr[5] = " + arr[5]);
        } catch (NullPointerException ex) {
            System.out.println(ex);
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println(ex);
        } finally {
            System.out.println("Khối lệnh finally luôn được thực thi");
        }
 
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

java.lang.ArrayIndexOutOfBoundsException: 5
Khối lệnh finally luôn được thực thi
Finished!

4.6. Một số lưu ý

  • Tại một thời điểm chỉ xảy ra một ngoại lệ và tại một thời điểm chỉ có một khối catch được thực thi. Khi exception đã bị bắt ở một catch thì các catch tiếp theo sẽ không được bắt.
  • Tất cả các khối catch phải được sắp xếp từ cụ thể nhất đến chung nhất (từ exception con đến exception cha), tức là phải khai báo khối lệnh catch để xử lý lỗi NullPointerException, ArithmeticException, … trước khi khai báo catch để xử lý lỗi Exception.
  • Khối lệnh finally luôn được thực thi dù chương trình có xảy ra ngoại lệ hay không (ngay cả sử dụng lệnh return).
  • Đối với mỗi khối try, có thể không có hoặc nhiều khối catch, nhưng chỉ có một khối finally.
  • Khối finally sẽ không được thực thi nếu chương trình bị thoát bằng cách gọi System.exit() hoặc xảy ra một lỗi (Error) không thể tránh khiến chương trình bị chết.

4.6.1. Ví dụ sử dụng System.exit() để không thực thi khối lệnh finally

public class ExceptionExample1 {
 
    public static void main(String[] args) {
        try {
            int arr[] = new int[5];
            arr[5] = 4;
            System.out.println("arr[5] = " + arr[5]);
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println(ex);
            System.exit(0); // Khối finally sẽ không được thực thi
        } finally {
            System.out.println("Khối lệnh finally luôn được thực thi");
        }
 
        System.out.println("Finished!");
    }
 
}

Kết quả thực thi chương trình trên:

java.lang.ArrayIndexOutOfBoundsException: 5

4.6.2. Ví dụ khối finally vẫn được thực thi dù gọi lệnh return

public class ExceptionExample1 {
     
    public static int getValueAtIndex5() {
        try {
            int arr[] = new int[5];
            arr[5] = 4;
            System.out.println("arr[5] = " + arr[5]);
            return 1;
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println(ex);
            return 0; // Return kết quả nhưng vẫn thực thi khối finally
        } finally {
            System.out.println("Khối lệnh finally luôn được thực thi");
        }
    }
 
    public static void main(String[] args) {
        System.out.println("getValueAtIndex5() = " + getValueAtIndex5());
    }
 
}

Kết quả thực thi chương trình trên:

java.lang.ArrayIndexOutOfBoundsException: 5
Khối lệnh finally luôn được thực thi
getValueAtIndex5() = 0

1 Trackback / Pingback

  1. Một số tính năng mới về xử lý ngoại lệ trong Java 7 – Blog của VietMX

Comments are closed.