Xử lý ngoại lệ trong Java giúp bạn handle được các trường hợp không mong muốn. Chương trình vẫn có thể hoạt động được một cách bình thường mặc dù đã có lỗi xảy ra ở một module nào đó. Sử dụng khối try-catch, try-catch-finally, try-finally hoặc từ khóa throws để xử lý. Tùy thuộc vào từng tình huống mà chúng ta lựa chọn cách dùng các khối này.

Exception - Ngoại lệ là gì?

Exception là một sự kiện chỉ xảy ra trong quá trình chương trình Java thực thi một câu lệnh nào đó và thông thường nó sẽ phá vỡ luồng làm việc của chương trình.

Ở một câu lệnh bất kỳ nếu xảy ra lỗi, chương trình sẽ tạo ra một Object và đưa nó vào Runtime System. Object này được gọi là Exception Object, nó sẽ chứa tất cả thông tin về lỗi, trạng thái của chương trình khi xảy ra lỗi. Nếu như bạn không handle Exception thì chương trình sẽ ngừng lại và thông báo lỗi.

Ví dụ: Giả sư như bạn có câu lệnh double a = b/c;. Nếu như người dùng nhập vào b = 3 và c = 0 chẳng hạn thì chương trình sẽ bị lỗi và ném ra cho chúng một exception có tên là java.lang.ArithmeticException.

Hệ thống phân cấp lớp Exception
Hệ thống phân cấp lớp Exception

Java có những loại Exception nào?

Trong Java có 2 loại Exception là Checked Exception và UnChecked Exception.
Checked và Unchecked Exception

Checked exceptions: là các Exception xảy ra tại thời điểm Compile time. Những Exception này bắt buộc chúng ta phải handle nó không được bỏ qua. Đựa vào mô hình phân cấp ở trên chúng ta có thể lấy ví dụ một số Checked Exception như là: IOException, FileNotFoundException,....

Unchecked exceptions: Là các Exception xảy ra tại thời điểm Runtime. Những exception này thường liên quan đến programming bug hoặc lỗi Logic. Đây là những lỗi không bắt buộc chúng ta phải handle.

Ví dụ: IndexOutOfBoundsException, NumberFormatException,...

Errors: Đây là những vấn đề rất nghiêm trọng liên quan đến môi trường thực thi của ứng dụng. Thông thường khi gặp những lỗi này, chương trình của chúng ta sẽ chết rất đột ngột.
Ví dụ: OutOfMemoryError, LinkageError, and StackOverflowError.

Xử lý ngoại lệ trong Java

Như đã đề cập ở đầu bài viết, để bắt được các Exception chúng ta cần sử dụng các khối try-catchfinally.
Kể từ Java 7 trở đi, chúng ta có thêm một tùy chọn mới đó là try-with-resources. Nó được sử dụng cho các trường hợp là đối tượng của interface Closeable resouces ví như là streams chẳng hạn.

Sử dụng khối try...catch để xử lý
try {
  //Khối lệnh có thể ném ra Exception
} catch (tên_exception tên_đối_tượng) {
  //Khối lệnh xử lý ngoại lệ
}

Ví dụ đoạn lệnh không dùng try...catch
a = b / c;
System.out.println("Cau lenh sau phep chia"); // (*)
Câu lệnh ở dòng thứ 2 (*) sẽ không thực hiện được nếu như bạn truyền vào mẫy số c = 0. Lúc này chương trình sẽ ngay lập tức bị dừng và thông báo lỗi của hệ thống.

Ví dụ đoạn lệnh dùng try...catch
try {
  a = b / c;
} catch (Exception e) {
  System.out.println("Co loi xay ra: " + e);
}
System.out.println("Cau lenh sau phep chia"); // (*)

Chương trình sẽ tiếp tục thực thi câu lệnh (*) mặc dù phép chia có bị lỗi hay không.

One try and multiple catch blocks
Trong một đoạn code, có thể xảy ra nhiều Exception, lúc này chúng ta có thể dùng nhiều khối catch để bắt và xử lý riêng biệt các ngoại lệ này.

Các khối catch thường được sắp xếp theo thứ tự xuất hiện. Và đặt các ngoại lệ là lớp con trước
Ví dụ: BException extends AException thì khối catch của BException phải đứng trước AException.
package com.vanthuong.example;

/**
 * Official blog: http://www.vanthuong.com
 * Learn and share your java knowledge
 */
public class ExceptionExample {
  public static void main(String[] args) {
    try {
      int b = 0;
      int a = 10 / b;
      System.out.println("a = " + a);

      int numbers[] = {1, 2, 3};
      numbers[3] = 10;

      System.out.println("Numbers = " + numbers);
    } catch (ArithmeticException ex) {
      System.out.println("Handle exception / by zero: " + ex.getClass().getName());
    } catch (ArrayIndexOutOfBoundsException ex) {
      System.out.println("Handle exception Array index out of bounds: " + ex.getClass().getName());
    }
  }
}

Ví dụ 2
package com.vanthuong.example;

/**
 * Official blog: http://www.vanthuong.com
 * Learn and share your java knowledge
 */
public class ExceptionExample {
  public static void main(String[] args) {
    try {
      divide(3, 1);
      displayInformation(null);
    } catch (ArithmeticException ex) {
      System.out.println("Handle exception / by zero: " + ex.getClass().getName());
    } catch (Exception ex) {
      System.out.println("Handle exception: " + ex.getClass().getName());
    }
  }

  public static void divide(int tuSo, int mauSo) {
    int a = tuSo / mauSo;
    System.out.println("ket qua = " + a);
  }

  public static void displayInformation(Object object) {
    System.out.println(object.toString());
  }
}

Trong ví dụ trên các bạn có thể thấy rắng khi tôi gọi phương thức divide(3, 1) thì nó sẽ xuất ném ra bất kỳ ngoại lệ nào. Trong khi đó, phương thức displayInformation() được tôi gọi với argument null, thì nó sẽ ném ra một ngoại lệ là NullPointerException. Nhưng khối catch thực sự xử lý ngoại lệ này chính là cacth (Exception e).

Với 2 ví dụ trên tôi tin chắc rắng các bạn đã biết cách xử lý ngoại lệ như thế nào rồi đúng không. Tuy nhiên, chúng ta còn có thể viết các khối try...catch lồng nhau.

Hiện tại, chúng ta chưa thấy bất khi ví dụ nào sử dụng khối finally. Vậy, khi nào thì chúng ta sẽ sử dụng nó? Mời bạn xem ví dụ bên dưới đây để biết thêm về try...catch...finally

package com.vanthuong.example;

import java.io.FileWriter;
import java.io.IOException;

/**
 * Official blog: http://www.vanthuong.com
 * Learn and share your java knowledge
 */
public class ExceptionExample {
  public static void main(String[] args) {
    FileWriter fw = null;
    try {
      fw = new FileWriter("data.txt");
      fw.write("Xu ly ngoai le trong java");
    } catch (IOException ioe) {
      System.out.println("Loi ghi file: " + ioe);
    } finally {
      try {
        if (fw != null) {
          fw.close();
        }
      } catch (IOException ioe) {
        System.out.println("Co loi xay ra: " + ioe);
      }
    }
  }
}
Trong đoạn code ví dụ trên bạn thấy rằng, tôi đang sử dụng FileWriter để ghi dữ liệu xuống file data.txt. Nhưng nếu khi thực hiện ghi file có lỗi thì nó sẽ ném ra một Exception là IOException. OK, tới đây chúng ta có khối cach (IOException ioe) để xử lý ngoại lệ này. Nhưng trong trường hợp đã ghi hoặc ghi bị lỗi thì tôi muốn chắc chắn rằng đối tượng fw phải được close để giải phóng bộ nhớ.

Lúc này, khối finally sẽ thực hiện nhiệm vụ của nó giúp chúng ta close đối tượng của FileWriter đã tạo ra. Trong Java, khối finally luôn luôn thực hiện khi có mặt trong try-catch-finally hoặc try-finally. Mục đích của khối finally là để chắc chắc chắn rằng những operation trong khối này được thực thi ngay cả khi chương trình có ném ra exception hay không.

Sử dụng từ khóa throws và throw
Trong trường hợp chúng ta không muốn xử lý Exception mà phương thức đã bắt được thì có thể ném nó ra lại cho phương thức gọi nó (caller method) xử lý. Hoặc trong một trường hợp khác là chúng ta đã sử lý nhưng sẽ ném ra một Exception đã customize thì từ khóa throws throw sẽ đảm nhiệm vấn đề này.

Ví dụ: Cũng với ví dụ trên, tôi sẽ tách nó ra một phương thức mới hoàn toàn và sử dụng từ khóa throws như sau:
package com.vanthuong.example;

import java.io.FileWriter;
import java.io.IOException;

public class ExceptionExample {
  public static void main(String[] args) {
    try {
      writeToFile();
    } catch (IOException ioe) {
      System.out.println("Da co loi xay ra khi thuc hien ghi file" + ioe);
    }
  }

  private static void writeToFile() throws IOException {
    FileWriter fileWriter = new FileWriter("data.txt");
    fileWriter.write("Xu ly ngoai le trong Java");
    fileWriter.close();
  }
}


Trong ví dụ tiếp theo, tôi sẽ đề cập đến từ khóa throw. Khi nào thì chúng ta sử dụng nó? từ khóa throw khi được sử dụng khi bạn muốn ném ra một ngoại lệ đã được xử lý (nói một cách khác là dùng customized Exception) cho lớp đang gọi nó xử lý.
package com.vanthuong.example;

import java.io.FileWriter;

public class ExceptionExample {
  public static void main(String[] args) {
    try {
      writeToFile();
    } catch (CustomException ioe) {
      System.out.println(ioe.getMessage());
    }
  }

  private static void writeToFile() throws CustomException {
    try {
      FileWriter fileWriter = new FileWriter("data.txt");
      fileWriter.write("Xu ly ngoai le trong Java");
      fileWriter.close();
    } catch (Exception e) {
      throw new CustomException("Da co loi xay ra khi thuc hien ghi file: " + e.getMessage());
    }
  }
}

OK, như vậy là chúng ta vừa tìm hiểu xong khái niệm về Exception, cách xử lý các Exception như thế nào. Hi vọng rằng các bạn sẽ hiểu được những gì mà tôi muốn chia sẻ. Trong bài viết tiếp theo, tôi sẽ hướng dẫn các bạn cách tạo Custom Exception và xử lý nó trong những tình huống mà chúng ta cần. Các bạn chú ý đón xem nhé, cuối cùng xin chúc các bạn học tốt.
Nguyễn Văn Thương