Xem thêm

Từ khóa throw và throws trong Java

Huy Erick
Khám phá từ khóa throw trong Java Trong ngôn ngữ Java, từ khóa throw được sử dụng để ném ra một ngoại lệ (exception) cụ thể. Chúng ta có thể sử dụng từ khóa throw...

Khám phá từ khóa throw trong Java

Trong ngôn ngữ Java, từ khóa throw được sử dụng để ném ra một ngoại lệ (exception) cụ thể. Chúng ta có thể sử dụng từ khóa throw để ném một trong hai loại ngoại lệ: checked hoặc unchecked. Tuy nhiên, chủ yếu chúng ta sử dụng từ khóa throw để ném ngoại lệ tùy chỉnh, tức là ngoại lệ do người dùng tự định nghĩa.

Cú pháp sử dụng từ khóa throw:

throw exception;

Ví dụ, chúng ta có thể tạo một phương thức validate() với tham số truyền vào là giá trị integer. Nếu tuổi nhỏ hơn 18, chúng ta sẽ ném ra ngoại lệ ArithmeticException, nếu không, chúng ta sẽ in ra một thông báo "Welcome".

public class ThrowExample {
    static void validate(int age) {
        if (age < 18)
            throw new ArithmeticException("not valid");
        else
            System.out.println("welcome to vote");
    }

    public static void main(String args[]) {
        validate(13);
        System.out.println("rest of the code...");
    }
}

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

Exception in thread "main" java.lang.ArithmeticException: not valid
    at com.gpcoder.throwex.ThrowExample.validate(ThrowExample.java:7)
    at com.gpcoder.throwex.ThrowExample.main(ThrowExample.java:13)

Quá trình lan truyền Exception trong Java

Method Call Stack

Trước khi tìm hiểu về quá trình lan truyền Exception trong Java, chúng ta cần hiểu về Method Call Stack trong Java. Một ứng dụng Java bao gồm nhiều cấp (level) của phương thức, chúng được quản lý bởi một ngăn xếp gọi là Method Call Stack. Ngăn xếp là một danh sách mà ta giới hạn việc thêm vào hoặc loại bỏ một phần tử chỉ thực hiện tại một đầu của danh sách, đầu này gọi là đỉnh (TOP) của ngăn xếp.

Ví dụ sau minh họa việc gọi phương thức theo trình tự: main() gọi methodA(), methodA() gọi methodB(), và methodB() gọi methodC().

public class MethodCallStackDemo {
    public static void main(String[] args) {
        System.out.println("Enter main()");
        methodA();
        System.out.println("Exit main()");
    }

    public static void methodA() {
        System.out.println("Enter methodA()");
        methodB();
        System.out.println("Exit methodA()");
    }

    public static void methodB() {
        System.out.println("Enter methodB()");
        methodC();
        System.out.println("Exit methodB()");
    }

    public static void methodC() {
        System.out.println("Enter methodC()");
        System.out.println("Exit methodC()");
    }
}

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

Enter main()
Enter methodA()
Enter methodB()
Enter methodC()
Exit methodC()
Exit methodB()
Exit methodA()
Exit main()

Lan truyền ngoại lệ (exception propagation)

Khi một ngoại lệ xảy ra trong một phương thức, nó sẽ được ném ra từ phía trên của call stack (ngăn xếp chứa các phương thức gọi đến nhau). Nếu ngoại lệ không được bắt, nó sẽ giảm xuống ngăn xếp đến phương thức trước, và tiếp tục như vậy cho đến khi ngoại lệ được bắt hoặc chạm đến đáy của stack. Quá trình này được gọi là lan truyền ngoại lệ (exception propagation).

exception-propagation

Unchecked Exception được lan truyền trong Calling Chain

Ví dụ dưới đây minh họa việc lan truyền Unchecked Exception trong một chuỗi gọi phương thức:

class TestExceptionPropagation {
    void m() {
        int data = 50 / 0; // ArithmeticException
    }

    void n() {
        m();
    }

    void p() {
        try {
            n();
        } catch (Exception e) {
            System.out.println("exception handled");
        }
    }

    public static void main(String args[]) {
        TestExceptionPropagation obj = new TestExceptionPropagation();
        obj.p();
        System.out.println("normal flow...");
    }
}

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

exception handled
normal flow...

Trong ví dụ trên, ngoại lệ ArithmeticException (một Unchecked Exception) xảy ra trong phương thức m(), nơi mà ngoại lệ không được xử lý. Do đó, ngoại lệ được truyền đến phương thức n(), nhưng lại không được xử lý. Tiếp theo, nó tiếp tục được truyền đến phương thức p(), trong đó ngoại lệ được xử lý.

Ngoại lệ có thể được xử lý trong bất kỳ phương thức nào trong chuỗi gọi phương thức, bao gồm main(), p(), n(), và m().

Checked Exception không được lan truyền trong Calling Chain

Ví dụ dưới đây minh họa việc khai báo throws cho một Checked Exception và kiểm tra việc lan truyền ngoại lệ trong chuỗi gọi phương thức:

class TestExceptionPropagation {
    void m() throws java.io.IOException {
        throw new java.io.IOException("device error");
    }

    void n() throws java.io.IOException {
        m();
    }

    void p() {
        try {
            n();
        } catch (Exception e) {
            System.out.println("exception handled");
        }
    }

    public static void main(String args[]) {
        TestExceptionPropagation2 obj = new TestExceptionPropagation2();
        obj.p();
        System.out.println("normal flow");
    }
}

Khi thực thi chương trình trên, sẽ xảy ra lỗi biên dịch vì ngoại lệ không được xử lý trong phương thức main():

Compile Time Error

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ó cung cấp thông tin cho lập trình viên rằng có thể xảy ra một ngoại lệ, và lập trình viên nên cung cấp mã xử lý ngoại lệ để duy trì luồng bình thường của chương trình.

Trong Java, 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 nào như NullPointerException, đó là lỗi của lập trình viên do anh ta không thực hiện kiểm tra trước khi sử dụng mã.

Cú pháp sử dụng từ khóa throws:

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

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

Chỉ nên khai báo những ngoại lệ checked, vì:

  • Ngoại lệ unchecked nằm trong sự kiểm soát của bạn.
  • Lỗi (error) nằm ngoài sự kiểm soát của bạn, ví dụ bạn không thể làm 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ý trong 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ệ có thể xảy ra.

Ví dụ sử dụng từ khóa throws trong Java:

import java.io.IOException;

class ThrowsExample {
    void m() throws IOException {
        throw new IOException("device error"); // checked exception
    }

    void n() throws IOException {
        m();
    }

    void p() {
        try {
            n();
        } catch (Exception e) {
            System.out.println("exception handled");
        }
    }

    public static void main(String args[]) {
        ThrowsExample obj = new ThrowsExample();
        obj.p();
        System.out.println("normal flow...");
    }
}

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

exception handled
normal flow...

Lưu ý:

Khi 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:

  • Trường hợp bắt ngoại lệ, tức là xử lý ngoại lệ bằng cách sử dụng try/catch.
  • Trường hợp khai báo ném ngoại lệ, tức là sử dụng từ khóa throws với phương thức.

Ví dụ xử lý ngoại lệ với try/catch:

import java.io.IOException;

public class ThrowsExample {
    void method() throws IOException {
        throw new IOException("device error");
    }

    public static void main(String args[]) {
        try {
            ThrowsExample obj = new ThrowsExample();
            obj.method();
        } catch (Exception e) {
            System.out.println("exception handled");
        }
        System.out.println("normal flow...");
    }
}

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

exception handled
normal flow...

Ví dụ khai báo throws ngoại lệ:

  • Trường hợp ngoại lệ không xảy ra:
import java.io.IOException;

public class ThrowsExample {
    void method() throws IOException {
        System.out.println("device operation performed");
    }

    public static void main(String args[]) throws IOException {
        ThrowsExample obj = new ThrowsExample();
        obj.method();
        System.out.println("normal flow...");
    }
}

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

device operation performed
normal flow...
  • Trường hợp ngoại lệ xảy ra:
import java.io.IOException;

public class ThrowsExample {
    void method() throws IOException {
        throw new IOException("device error");
    }

    public static void main(String args[]) throws IOException {
        ThrowsExample obj = new ThrowsExample();
        obj.method();
        System.out.println("normal flow...");
    }
}

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

Exception in thread "main" java.io.IOException: device error
    at com.gpcoder.throwex.ThrowsExample.method(ThrowsExample.java:8)
    at com.gpcoder.throwex.ThrowsExample.main(ThrowsExample.java:13)

Sự khác nhau giữa throw và throws trong Java

Từ khóa throw:

  • Sử dụng để ném ra một ngoại lệ rõ ràng.
  • Ngoại lệ checked không được truyền ra nếu chỉ sử dụng từ khóa throw.
  • Sau throw là một instance.

Từ khóa throws:

  • Sử dụng để khai báo một ngoại lệ.
  • Ngoại lệ checked được truyền ra ngay cả khi chỉ sử dụng từ khóa throws.
  • Sau throws là một hoặc nhiều class.
  • Throws được khai báo ngay sau dấu đóng ngoặc đơn của phương thức.

Tóm lại, từ khóa throw được sử dụng để ném ra một ngoại lệ cụ thể, trong khi từ khóa throws được sử dụng để khai báo một ngoại lệ.

1