Từ khóa throw và throws trong Java

1. Từ khóa throw trong java

Từ khoá throw trong java được sử dụng để ném ra một ngoại lệ (exception) 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).

Cú pháp:

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&#91;&#93;) {
        validate(13);
        System.out.println("rest of the code...");
    }
 
}
&#91;/code&#93;
<!-- /wp:shortcode -->

<!-- wp:shortcode -->

throw exception;

Ví dụ: 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”.

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

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

2.1. Method Call Stack

Trước khi tìm hiểu về lan truyền Exception trong java, chúng ta hãy tìm hiểu Method Call back trong Java.

Một ứng dụng bao gồm nhiều cấp (level) của phương thức, chúng được quản lý bởi một cái gọi là ngăn xếp gọi phương thức (Method Call Stack). Ngăn xếp (Stack) 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.

Trong ví dụ sau, phương thức main() gọi methodA(); methodA() gọi methodB(); 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:

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

Như bạn thấy output ở chương trình trên:

  1. JVM gọi phương thức main()
  2. Phương thức main() được thêm vào ngăn xếp (Stack) trước khi gọi phương thức methodA()
  3. Phương thức methodA() được thêm vào ngăn xếp (Stack) trước khi gọi phương thức methodB()
  4. Phương thức methodB() được thêm vào ngăn xếp (Stack) trước khi gọi phương thức methodC()
  5. Phương thức methodC() hoàn thành
  6. Phương thức methodB() được xóa khỏi ngăn xếp (Stack) và hoàn thành.
  7. Phương thức methodA() được xóa khỏi ngăn xếp (Stack) và hoàn thành.
  8. Phương thức main() được xóa khỏi ngăn xếp (Stack) và hoàn thành.
  9. Chương trình kết thúc.

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

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

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

Ví dụ:

class TestExceptionPropagation {
    void m() {
        int data = 50 / 0;
    }
 
    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:

exception handled
normal flow...

Trong ví dụ trên, ngoại lệ ArithmeticException (đây là Unchecked Exception) xảy ra trong phương thức m() nơi nó không được xử lý, do đó nó được truyền đến phương thức n() nhưng nó không được xử lý, một lần nữa nó đượ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 call stack hoặc trong phương thức main(), phương thức p(), phương thức n() hoặc m().

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

Ví dụ:

class TestExceptionPropagation {
    void m() {
        throw new java.io.IOException("device error"); // checked exception
    }
 
    void n() {
        m();
    }
 
    void p() {
        try {
            n();
        } catch (Exception e) {
            System.out.println("exception handeled");
        }
    }
 
    public static void main(String args[]) {
        TestExceptionPropagation2 obj = new TestExceptionPropagation2();
        obj.p();
        System.out.println("normal flow");
    }
}

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

Compile Time Error

3. 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:

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

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:

exception handled
normal flow...

Lưu ý: 

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:

  • 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.

3.1. 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:

exception handled
normal flow...

3.2. Ví dụ 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 đó.

Ví dụ 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:

device operation performed
normal flow...

Ví dụ 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:

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

4. Sự khác nhau giữa throw và throws trong java

throwthrows
Từ khóa throw trong java được sử dụng để ném ra một ngoại lệ rõ ràng.Từ khóa throws trong java được sử dụng để khai báo một ngoại lệ.
Ngoại lệ checked không được truyền ra nếu chỉ sử dụng từ khóa throw.Ngoại lệ checked được truyền ra ngay cả khi chỉ sử dụng từ khóa throws.
Sau throw là một instance.Sau throws là một hoặc nhiều class.
Throw được sử dụng trong phương thức có thể quăng ra Exception ở bất kỳ dòng nào trong phương thức (sau đó dùng try-catch để bắt hoặc throws cho thằng khác sử lý)Throws được khai báo ngay sau dấu đóng ngoặc đơn của phương thức. Khi một phương thức có throw bên trong mà không bắt lại (try – catch) thì phải ném đi (throws) cho thằng khác xử lý.
Không thể throw nhiều exceptions.Có thể khai báo nhiều exceptions, Ví dụ:public void method() throws IOException, SQLException { }