Java OOP – Ngoại lệ (Exception)

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

Trong bài này, chúng ta sẽ tìm hiểu về ngoại lệ (Exception) trong java, các kiểu ngoại lệ và sự khác biệt giữa các ngoại lệ checked và unchecked.

Nội dung chính

Exception là gì?

Theo từ điển: Exception (ngoại lệ) là một tình trạng bất thường.

Trong java, ngoại lệ là một sự kiện làm gián đoạn luồng bình thường của chương trình. Nó là một đối tượng được ném ra tại runtime.


Exception Handling là gì?

Exception Handling (xử lý ngoại lệ) là một cơ chế xử lý các lỗi runtime như ClassNotFound, IO, SQL, Remote, vv


Lợi thế của Exception Handling trong java

Lợi thế cốt lõi của việc xử lý ngoại lệ là duy trì luồng bình thường của ứng dụng. Ngoại lệ thường làm gián đoạn luồng bình thường của ứng dụng đó là lý do tại sao chúng ta sử dụng xử lý ngoại lệ. Hãy xem xét kịch bản sau:

statement 1;  
statement 2;  
statement 3;  
statement 4;  
statement 5; //ngoại lệ xảy ra
statement 6;  
statement 7;  
statement 8;  
statement 9;  
statement 10;  

Giả sử có 10 câu lệnh trong chương trình của bạn và xảy ra trường hợp ngoại lệ ở câu lệnh 5, phần còn lại của chương trình sẽ không được thực thi, nghĩa là câu lệnh 6 đến 10 sẽ không chạy. Nếu chúng ta thực hiện xử lý ngoại lệ, phần còn lại của câu lệnh sẽ được thực hiện. Đó là lý do tại sao chúng ta sử dụng xử lý ngoại lệ trong java.


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

exception handling trong java

Các kiểu của ngoại lệ

Có hai loại ngoại lệ chính là: checked và unchecked. Còn Sun Microsystem nói rằng có ba loại ngoại lệ:

  1. Checked Exception
  2. Unchecked Exception
  3. Error

Sự khác nhau giữa các ngoại lệ checked và unchecked

1. Checked Exception

Các lớp extends từ lớp Throwable ngoại trừ RuntimeException và Error được gọi là checked exception, ví dụ như Exception, SQLException vv. Các checked exception được kiểm tra tại compile-time.

Ví dụ: ta có hàm testCheckedException() ném ra một ngoại lệ được extends từ lớp Exception, nên khi nó được gọi ra trong hàm main, trình biên dịch sẽ check(báo lỗi) tức là trình biên dịch sẽ nói ràng hàm này có ném lỗi ra đấy phải xử lý lỗi đi.

vi du checked exception trong java

2. Unchecked Exception

Các lớp extends từ RuntimeException được gọi là unchecked exception, ví dụ: ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException,… Các ngoại lệ unchecked không được kiểm tra tại compile-time mà chúng được kiểm tra tại runtime.

Ví dụ: ta có hàm testUncheckedException() ném ra một ngoại lệ được extends từ lớp RuntimeException, nên khi nó được gọi ra trong hàm main, trình biên dịch sẽ không check (không báo lỗi). Mà khi chạy nếu có lỗi sẽ bắn ra tại runtime.

vi du unchecked exception trong java

3. Error

Error là lỗi không thể cứu chữa được, ví dụ: OutOfMemoryError, VirtualMachineError, AssertionError, vv


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

Có một số kịch bản mà ngoại lệ unchecked có thể xảy ra. Như các trường hợp sau:

1. Kịch bản ArithmeticException xảy ra

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

int a=50/0;//ArithmeticException

2. Kịch bản NullPointerException xảy ra

Nếu chúng ta có 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.

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

3. Kịch bản NumberFormatException xảy ra

Sự định dạng sai của bất kỳ giá trị nào, có thể xảy ra NumberFormatException. Giả sử ta có 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 s="abc";  
int i=Integer.parseInt(s);//NumberFormatException  

Kịch bản ArrayIndexOutOfBoundsException xảy ra

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 như thể hiện dưới đây:

int a[]=new int[5];  
a[10]=50; //ArrayIndexOutOfBoundsException

Các từ khóa xử lý ngoại lệ trong java

Có 5 từ khóa được sử dụng để xử lý ngoại lệ trong java, đó là:

  1. try
  2. catch
  3. finally
  4. throw
  5. throws

Chúng ta sẽ học cách sử dụng các từ khóa này trong các bài tiếp theo…

Khối lệnh try-catch trong java

Nội dung chính

Khối lệnh try 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.

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

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

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

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

Khối lệnh catch trong java

Khối catch trong java được sử dụng để xử lý các Exception. Nó phải được sử dụng sau khối try.

Bạn có thể sử dụng nhiều khối catch với một khối try duy nhất.

Vấn đề không có ngoại lệ xử lý

public class TestTryCatch1 {
    public static void main(String args[]) {
        int data = 50 / 0;  // ném ra ngoại lê ở đây
        System.out.println("rest of the code...");
    }
}

Output:

Exception in thread "main" java.lang.ArithmeticException: / by zero
 at vn.tpv.exception1.TestTryCatch1.main(TestTryCatch1.java:5)

Trong ví dụ trên, phần còn lại của code không được thực thi (dòng chữ “rest of the code…” không được in ra màn hình). Tất cả các lệnh không được thực thi sau khi xảy ra ngoại lệ.

Giải quyết bằng xử lý ngoại lệ

<!-- wp:paragraph -->
<p><code>&nbsp;&nbsp;&nbsp;&nbsp;</code><code>}</code></p>
<!-- /wp:paragraph -->

Output:

java.lang.ArithmeticException: / by zero
rest of the code...

Trong ví dụ này, phần còn lại của code được thực thi nghĩa là dòng chữ “rest of the code…” được in ra màn hình.

Đa khối lệnh catch trong java

Nếu bạn phải thực hiện các tác vụ khác nhau mà ở đó có thể xảy ra các ngoại lệ khác nhau, hãy sử dụng đa khối lệnh catch trong java.

Hãy xem ví dụ sau về việc sử dụng nhiều khối lệnh catch trong java

public class TestMultipleCatchBlock {
    public static void main(String args[]) {
        try {
            int a[] = new int[5];
            a[5] = 30 / 0;
        } catch (ArithmeticException e) {
            System.out.println("task1 is completed");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("task 2 completed");
        } catch (Exception e) {
            System.out.println("common task completed");
        }
 
        System.out.println("rest of the code...");
    }
}

Output:

task1 is completed
rest of the code...

Quy tắc: Vào 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.

Quy tắc: 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ức là phải khai báo khối lệnh catch để xử lý lỗi ArithmeticException trước khi khai báo catch để xử lý lỗi Exception.

Ví dụ:

public class TestMultipleCatchBlock1 {
    public static void main(String args[]) {
        try {
            int a[] = new int[5];
            a[5] = 30 / 0;
        } catch (Exception e) {
            System.out.println("common task completed");
        } catch (ArithmeticException e) {
            System.out.println("task1 is completed");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("task2 is completed");
        }
        System.out.println("rest of the code...");
    }
}

Output:

Compile-time error

Chương trình trên bị lỗi tại compile-time là vì khi có ngoại lệ xảy ra thì các khối lệnh catch (ArithmeticException e) và catch (ArrayIndexOutOfBoundsException e) không bao giờ được thực thi, do khối catch (Exception e) đã bắt tất cả các ngoại lệ rồi.

Khối try trong một khối try được gọi là khối try lồng nhau trong java.

Nội dung chính

Tại sao phải sử dụng khối try lồng nhau

Đôi khi một tình huống có thể phát sinh khi một phần của một khối lệnh có thể xảy ra một lỗi và toàn bộ khối lệnh chính nó có thể xảy ra một lỗi khác. Trong những trường hợp như vậy, trình xử lý ngoại lệ phải được lồng nhau.

try {  
    statement 1;  
    statement 2;  
    try {  
        statement 1;  
        statement 2;  
    }  
    catch(Exception e) {
         
    }  
}  
catch(Exception e) {
     
}

Ví dụ về khối try lồng nhau trong java

Hay xem ví dụ đơn giản sau về khối lệnh try lồng nhau.

public class TestException {
    public static void main(String args[]) {
        try {
            try {
                System.out.println("Thuc hien phep chia");
                int b = 39 / 0;
            } catch (ArithmeticException e) {
                System.out.println(e);
            }
 
            try {
                int a[] = new int[5];
                a[5] = 4;
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println(e);
            }
 
            System.out.println("khoi lenh khac");
        } catch (Exception e) {
            System.out.println("xy ly ngoai le");
        }
 
        System.out.println("tiep tuc chuong trinh..");
    }
}

Khối lệnh finally trong java

Khối lệnh finally trong java được sử dụng để thực thi các lệnh quan trọng như đóng kết nối, đóng cá stream,…

Khối lệnh finally trong java luôn được thực thi cho dù có ngoại lệ xảy ra hay không hoặc gặp lệnh return trong khối try.

Khối lệnh finally trong java được khai báo sau khối lệnh try hoặc sau khối lệnh catch.

flow của khối lệnh finally trong java

Note: Nếu bạn không xử lý ngoại lệ, trước khi kết thúc chương trình, JVM thực thi khối finally (nếu có).


Nội dung chính

Tại sao phải sử dụng khối finally

Khối finally có thể được sử dụng để chèn lệnh “cleanup” vào chương trình như việc đóng file, đóng các kết nối,…


Cách sử dụng khối finally trong java

Dưới đây là các trường hợp khác nhau về việc sử dụng khối finally trong java.

TH1

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

public class TestFinallyBlock {
    public static void main(String args[]) {
        try {
            int data = 25 / 5;
            System.out.println(data);
        } catch (NullPointerException e) {
            System.out.println(e);
        } finally {
            System.out.println("finally block is always executed");
        }
        System.out.println("rest of the code...");
    }
}

Output:

5
finally block is always executed
rest of the code...

TH2

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

public class TestFinallyBlock1 {
    public static void main(String args[]) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        } catch (NullPointerException e) {
            System.out.println(e);
        } finally {
            System.out.println("finally block is always executed");
        }
        System.out.println("rest of the code...");
    }
}

Output:

finally block is always executed
Exception in thread "main" java.lang.ArithmeticException: / by zero

TH3

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

public class TestFinallyBlock2 {
    public static void main(String args[]) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        } catch (ArithmeticException e) {
            System.out.println(e);
        } finally {
            System.out.println("finally block is always executed");
        }
        System.out.println("rest of the code...");
    }
}

Output:

java.lang.ArithmeticException: / by zero
finally block is always executed
rest of the code...

TH4

Sử dụng khối lệnh finally trong trường hợp trong khối try có lệnh return.

public class TestFinallyBlock3 {
    public static void main(String args[]) {
        try {
            int data = 25;
            if (data % 2 != 0) {
             System.out.println(data + " is odd number");
             return;
            }
        } catch (ArithmeticException e) {
            System.out.println(e);
        } finally {
            System.out.println("finally block is always executed");
        }
        System.out.println("rest of the code...");
    }
}

Output:

25 is odd number
finally block is always executed

Từ khóa throw trong java

Nội dung chính

Từ khóa throw trong java

Từ khoá throw trong java được sử dụng để ném ra một ngoại lệ cụ thể.

Chúng ta có thể ném một trong hai ngoại lệ checked hoặc unchecked trong java bằng từ khóa throw. Từ khóa throw chủ yếu được sử dụng để ném ngoại lệ tùy chỉnh (ngoại lệ do người dùng tự định nghĩa). Chúng ta sẽ học ngoại lệ tùy chỉnh trong bài sau.

Cú pháp từ khóa throw:

throw exception;

Ví dụ về throw IOException.

throw new IOException("File không tồn tại");

Ví dụ về từ khóa throw trong java

Ví dụ 1: throw ra ngoại lệ nhưng không xử lý

Trong ví dụ này, chúng ta tạo ra phương thức validate() với tham số truyền vào là giá trị integer. Nếu tuổi dưới 18, chúng ta ném ra ngoại lệ ArithmeticException nếu không in ra một thông báo “welcome”.

public class TestThrow1 {
    static void validate(int age) {
        if (age < 18)
            throw new ArithmeticException("not valid");
        else
            System.out.println("welcome");
    }
 
    public static void main(String args[]) {
        validate(13);
        System.out.println("rest of the code...");
    }
}

Output:

Exception in thread "main" java.lang.ArithmeticException: not valid

Ví dụ 1: throw ra ngoại lệ nhưng có xử lý

public class TestThrow2 {
    static void validate(int age) {
        try {
            if (age < 18)
                throw new ArithmeticException("not valid");
            else
                System.out.println("welcome");
        } catch (ArithmeticException ex) {
            System.out.println(ex.getMessage());
        }
    }
 
    public static void main(String args[]) {
        validate(13);
        System.out.println("rest of the code...");
    }
}

Output:

not valid
rest of the code...
Từ khóa throws trong java

Từ khóa throws trong java

Nội dung chính

Từ khóa throws trong java

Từ khóa throws trong java được sử dụng để khai báo một ngoại lệ. Nó thể hiện thông tin cho lập trình viên rằng có thể xảy ra một ngoại lệ, vì vậy nó là tốt hơn cho các lập trình viên để cung cấp các mã xử lý ngoại lệ để duy trì luồng bình thường của chương trình.

Exception Handling chủ yếu được sử dụng để xử lý ngoại lệ checked. Nếu xảy ra bất kỳ ngoại lệ unchecked như NullPointerException, đó là lỗi của lập trình viên mà anh ta không thực hiện kiểm tra trước khi code được sử dụng.

Cú pháp của throws trong java

return_type method_name() throws exception_class_name {  
    / /method code
}

Ngoại lệ nào nên được khai báo

Chỉ ngoại lệ checked, bởi vì:

  • Ngoại lệ unchecked: nằm trong sự kiểm soát của bạn.
  • error: nằm ngoài sự kiểm soát của bạn, ví dụ bạn sẽ không thể làm được bất kì điều gì khi các lỗi VirtualMachineError hoặc StackOverflowError xảy ra.

Lợi ích của từ khóa throws trong java

  • Ngoại lệ checked có thể được ném ra ngoài và được xử lý ở một hàm khác.
  • Cung cấp thông tin cho caller của phương thức về các ngoại lệ.

Ví dụ về từ khóa throws trong java

Duới đây là ví dụ về mệnh đề throws trong java mô tả rằng ngoại lệ checked có thể được truyền ra bằng từ khóa throws.

import java.io.IOException;
 
public class TestThrows1 {
    void m() throws IOException {
        throw new IOException("Loi thiet bi");// checked exception
    }
 
    void n() throws IOException {
        m();
    }
 
    void p() {
        try {
            n();
        } catch (Exception e) {
            System.out.println("ngoai le duoc xu ly");
        }
    }
 
    public static void main(String args[]) {
        TestThrows1 obj = new TestThrows1();
        obj.p();
        System.out.println("luong binh thuong...");
    }
}

Output:

ngoai le duoc xu ly
luong binh thuong...

Quy tắc: Nếu bạn đang gọi một phương thức khai báo throws một ngoại lệ, bạn phải bắt hoặc throws ngoại lệ đó.

Có hai trường hợp:

  • TH1: Bạn đã bắt ngoại lệ, tức là xử lý ngoại lệ bằng cách sử dụng try/catch.
  • TH2: Bạn khai báo ném ngoại lệ, tức là sử dụng từ khóa throws với phương thức.

TH1: xử lý ngoại lệ với try/catch

Trong trường hợp bạn xử lý ngoại lệ, code sẽ được thực thi tốt cho dù ngoại lệ có xuất hiện trong chương trình hay không.

import java.io.IOException;
 
class M {
    void method() throws IOException {
        throw new IOException("Loi thiet bi");
    }
}
 
public class TestThrows2 {
    public static void main(String args[]) {
        try {
            M m = new M();
            m.method();
        } catch (Exception e) {
            System.out.println("Ngoai le duoc xu ly");
        }
 
        System.out.println("Luong binh thuong...");
    }
}

Output:

Ngoai le duoc xu ly
Luong binh thuong...

TH2: Khai báo throws ngoại lệ

  • A) Trong trường hợp bạn khai báo throws ngoại lệ, nếu ngoại lệ không xảy ra, code sẽ được thực hiện tốt.
  • B) Trong trường hợp bạn khai báo throws ngoại lệ, nếu ngoại lệ xảy ra, một ngoại lệ sẽ được ném ra tại runtime vì throws nên không xử lý ngoại đó.
import java.io.IOException;
 
class M {
    void method() throws IOException {
        System.out.println("Thiet bi dang hoat dong tot");
    }
}
 
public class TestThrows2 {
    public static void main(String args[]) throws IOException {
        M m = new M();
        m.method();
        System.out.println("Luong binh thuong...");
    }
}

Output:

Thiet bi dang hoat dong tot
Luong binh thuong...

Ngoại lệ xảy ra

import java.io.IOException;
 
class M {
    void method() throws IOException {
        throw new IOException("Thiet bi");
    }
}
 
public class TestThrows2 {
    public static void main(String args[]) throws IOException {
        M m = new M();
        m.method();
        System.out.println("Luong binh thuong...");
    }
}

Output:

Exception in thread "main" java.io.IOException: Thiet bi