Sneaky Throws trong Java – Tính năng khá thú vị

Nếu đã tìm hiểu qua exception trong Java thì chắc hẳn các bạn đều biết nó được chia thành 2 loại checked exceptionuncheck exception. Checked exception là những exception có thể nhìn thấy và kiểm tra tại thời điểm biên dịch. Trong khi unchecked exception là các exception không được kiểm tra tại thời điểm biên dịch.

Do vậy, khi một hàm throw một checked exception bên trong thân hàm bắt buộc chúng ta phải xử lý bằng try-catch hoặc chỉ định chúng với từ khoá throws trong phần chữ ký của hàm.

public static void test() throws IOException {
    throw new IOException("File not found");
}

Vậy có cách nào để một method có chứa mã code throw ra một checked exception mà không cần thêm chúng vào phần chữ ký của method hoặc sử dụng try-catch không? Chúng ta sẽ cùng nhau tìm hiểu khái niệm Sneaky Throws trong Java để xem nó sẽ giúp gì trong trường hợp này nào.

Sneaky Throws

Sneaky throws là một khái niệm cho phép throw ra các checked exception mà không cần định nghĩa chúng trong phần chữ ký của method. Cơ chế này hoạt động giống như các unchecked exception, thì như đã biết một method chứa mã throw unchecked exception thì không bắt buộc phải khai báo chúng ở phần chữ ký của hàm

public static void test1 () {
    throw new UnsupportedOperationException("message");
}

Triển khai sneaky throws

Java 8 mang đến quy tắc suy diễn mới, trong đó nếu một method ném ra một exception kiểu generic T thì nó được xem là một trong những subclass của RuntimeException (supper class của các unchecked exception). Do vậy chúng ta có thể dựa vào tính chất này để triển khai sneaky throws.

public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
    throw (E) e;
}

private static void throwsSneakyIOException() {
    sneakyThrow(new IOException("sneaky"));
}

Như ví dụ trên, sneakyThrow() method throws E sẽ được hiểu là một RuntimeException. Bên cạnh đó, E là kiểu generic extends từ Throwable. Có nghĩa là chúng ta vẫn có thể chỉ định một checked exception như trên IOException là một checked exception.

Chúng ta có thể kiểm thử bẳng

import java.io.IOException;

public class Main {

    public static void main(String args[]) {
        try {
            throwsSneakyIOException();
        } catch (Exception ex) {
            System.out.println(ex.getMessage().toString());
        }
    }


    public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
        throw (E) e;
    }

    private static void throwsSneakyIOException() {
        sneakyThrow(new IOException("sneaky"));
    }
}



Output

sneaky

@SneakyThrows Lombok annotation

@SneakyThrows annotation từ Lombok cho phép chúng ta triển khai cơ chế sneaky throws tương tự như ở phần trên đã đề cập.

import lombok.SneakyThrows;

import java.io.IOException;

public class Main {

    public static void main(String args[]) {
        try {
            throwsSneakyIOException();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }


    @SneakyThrows
    private static void throwsSneakyIOException() {
        throw new IOException("File Not found");
    }
}



Một hạn chế ở đây là chúng ta không thể chỉ định trực tiếp một checked exception trong khối try-catch khi xử lý các sneaky throws.

import lombok.SneakyThrows;

import java.io.IOException;

public class Main {

    public static void main(String args[]) {
        try {
            throwsSneakyIOException();
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }


    @SneakyThrows
    private static void throwsSneakyIOException() {
        throw new IOException("File Not found");
    }
}

Như ví dụ này thì trình biên dịch sẽ báo lỗi

"java.io.IOException is never thrown in body of corresponding try statement"

Vì vậy cách giải quyết chỗ này là chúng ta sử dụng một supper class cao hơn IOException là exception như ví dụ phần đầu.

Một ví dụ tuyệt vời để chúng ta có thể thấy được công dụng của sneaky throws, giả sử chúng ta đang triển khai một class implement từ Runnable interface. Trong đó mình muốn throw ra một InterruptedException trong run() method  và mong muốn bên ngoài (nơi sử dụng nó) xử lý ngoại lệ. Điều này là không thể bởi vì run() được định nghĩa ở Runnable interface không hề chứa bất kỳ một exception nào trong phần khai.

 public class SneakyRunnable implements Runnable {
    public void run() throws InterruptedException {
    }
}

Cú pháp khai báo trên sẽ gây ra lỗi không thể biên dịch. Rất may mắn chúng ta có thể áp dụng @SneakyThrows trong trường hợp này.

import lombok.SneakyThrows;

public class Main {

    public static void main(String args[]) {
        try {
            new SneakyRunnable().run();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}


class SneakyRunnable implements Runnable {
    @SneakyThrows
    public void run() {
        throw new InterruptedException("InterruptedException");
    }
}


Output

InterruptedException

Kết bài

Qua bài viết ngắn này chúng ta đã biết một thủ thuật nhỏ đánh lừa trình biên dịch giữa checked exception và unchecked exception. Để triển khai chúng ta cần phiên bản Java 8 trở nên hoặc có thể sử dụng lombok cho tiện lợi cũng được.

Nguồn

http://www.javabyexamples.com/lombok-sneakythrows

https://www.baeldung.com/java-sneaky-throws

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x