Có cách nào để làm cho Runnable's run () ném một ngoại lệ không?


80

Một phương thức tôi đang gọi trong run () trong một lớp triển khai Runnable ) được thiết kế để ném một ngoại lệ.

Nhưng trình biên dịch Java sẽ không cho phép tôi làm điều đó và đề nghị tôi sử dụng try / catch.

Vấn đề là bằng cách bao quanh nó bằng một thử / bắt tôi làm cho việc chạy () cụ thể đó trở nên vô dụng. Tôi làm muốn ném ngoại lệ đó.

Nếu tôi chỉ định throwscho run () chính nó, trình biên dịch sẽ phàn nàn điều đó Exception is not compatible with throws clause in Runnable.run().

Thông thường, tôi hoàn toàn ổn khi không để run () ném ra một ngoại lệ. Nhưng tôi có một tình huống duy nhất mà tôi phải có chức năng đó.

Làm cách nào để khắc phục hạn chế này?


Ngoài các câu trả lời khác, để theo dõi tiến trình của nhiệm vụ, bạn có thể sử dụng lớp FutureTask.
JProgrammer,

Câu trả lời:


26

Nếu bạn muốn vượt qua một lớp triển khai Runnablevào Threadkhuôn khổ, thì bạn phải chơi theo các quy tắc của khuôn khổ đó, hãy xem câu trả lời của Ernest Friedman-Hill tại sao làm điều đó theo cách khác là một ý tưởng tồi.

Tuy nhiên, tôi có linh cảm rằng bạn muốn gọi runphương thức trực tiếp trong mã của mình, vì vậy mã gọi của bạn có thể xử lý ngoại lệ.

Câu trả lời cho vấn đề này là dễ dàng. Không sử dụng Runnablegiao diện từ thư viện Thread, nhưng thay vào đó hãy tạo giao diện của riêng bạn với chữ ký đã sửa đổi cho phép ném ngoại lệ đã kiểm tra, ví dụ:

public interface MyRunnable
{
    void myRun ( ) throws MyException;
}

Bạn thậm chí có thể tạo một bộ điều hợp chuyển đổi giao diện này thành thực Runnable(bằng cách xử lý ngoại lệ đã kiểm tra) phù hợp để sử dụng trong khung Thread.


Một giải pháp đơn giản như vậy đã đến chỉ bằng cách không nghĩ đến "trong hộp". Tất nhiên, a Runnablechỉ là một giao diện đơn giản và chúng ta có thể tự tạo. Không hữu ích cho trường hợp sử dụng luồng nhưng để chuyển các đoạn mã "chạy được" khác nhau xung quanh điều này là hoàn hảo.
Richard Le Mesurier

82

Bạn có thể sử dụng một Callablethay thế, gửi nó cho một ExecutorServicevà chờ đợi kết quả với FutureTask.isDone()trả về bởi ExecutorService.submit().

Khi isDone()trả về true bạn gọi FutureTask.get(). Bây giờ, nếu bạn Callableđã ném một cái Exceptionthì FutureTask.get()wiill cũng ném một cái Exceptionvà Exception ban đầu, bạn sẽ có thể truy cập bằng cách sử dụng Exception.getCause().


23

Nếu run()ném một ngoại lệ đã kiểm tra, điều gì sẽ bắt được nó? Không có cách nào để bạn gửi run()lời gọi đó vào một trình xử lý, vì bạn không viết mã gọi nó.

Bạn có thể bắt ngoại lệ đã kiểm tra của mình trong run()phương thức và ném một ngoại lệ không bị ràng buộc (tức là RuntimeException) vào vị trí của nó. Điều này sẽ kết thúc luồng bằng một dấu vết ngăn xếp; có lẽ đó là những gì bạn đang theo đuổi.

Thay vào đó, nếu bạn muốn run()phương thức của mình báo lỗi ở đâu đó, thì bạn chỉ có thể cung cấp phương thức gọi lại để khối run()của phương thức catchgọi; phương thức đó có thể lưu trữ đối tượng ngoại lệ ở đâu đó và sau đó chuỗi quan tâm của bạn có thể tìm thấy đối tượng ở vị trí đó.


2
Phần đầu tiên không phải là một lập luận tốt. "Nếu main()ném một ngoại lệ đã kiểm tra, điều gì sẽ bắt được nó?" "Nếu run()ném một ngoại lệ không được chọn, điều gì sẽ bắt được nó?"
Christian Hujer

17

Có, có một cách để ném một ngoại lệ đã kiểm tra khỏi run()phương thức, nhưng nó quá khủng khiếp nên tôi sẽ không chia sẻ nó.

Đây là những gì bạn có thể làm thay thế; nó sử dụng cùng một cơ chế mà một ngoại lệ thời gian chạy sẽ thực hiện:

@Override
public void run() {
  try {
    /* Do your thing. */
    ...
  } catch (Exception ex) {
    Thread t = Thread.currentThread();
    t.getUncaughtExceptionHandler().uncaughtException(t, ex);
  }
}

Như những người khác đã lưu ý, nếu run()phương pháp của bạn thực sự là mục tiêu của a Thread, thì không có lý do gì để ném một ngoại lệ vì nó không thể quan sát được; ném một ngoại lệ có tác dụng tương tự như không ném một ngoại lệ (không có).

Nếu nó không phải là Threadmục tiêu, không sử dụng Runnable. Ví dụ, có lẽ Callablelà phù hợp hơn.


Nhưng điều này có gây ra sự cố cho quá trình khi nó ném không?
Dinesh

@DineshVG Không, chỉ một lỗi trong JVM mới có thể gây ra sự cố thực sự. Bộ xử lý ngoại lệ mặc định chỉ in ngoại lệ. Nếu bạn đã quen với quá trình thoát ra sau đó, thì đó là vì luồng đó là luồng duy nhất đang chạy và nó đã kết thúc.
erickson

Tôi đã thử (trong các trường hợp kiểm tra thiết bị Android) bằng cách sử dụng điều này cho cuộc gọi xà phòng, trong đó nếu tôi nhận được số 400 từ cuộc gọi Soap, tôi sẽ ném ra một ngoại lệ. Lệnh gọi xà phòng này được gọi từ một luồng khi khởi động trường hợp thử nghiệm. Chủ đề này sử dụng nó t.getUncaughtExceptionHandler().uncaughtException(t, ex);để ném nó vào trường hợp kiểm tra thiết bị đo đạc. Thêm một dòng này khiến quá trình bị lỗi !. Không biết tại sao.
Dinesh

@DineshVG Trong môi trường đó, có Thread.getDefaultUncaughtExceptionHandler()trả về nullkhông? Nếu không, loại kết quả là gì? Điều gì xảy ra nếu, thay vì gọi uncaughtException(), bạn bọc ngoại lệ đã chọn vào a RuntimeExceptionvà ném nó đi?
erickson

1
@DineshVG Android có thể đang đặt một trình xử lý ngoại lệ không cần thiết mặc định thực hiện điều này. Đó là lý do tại sao tôi hỏi nếu Thread.getDefaultUncaughtExceptionHandler()trả lại null; nếu không, Android đang cung cấp chế độ mặc định, để cung cấp báo cáo, v.v. Nhưng bạn có thể đặt nó làm những gì bạn muốn. Có thêm thông tin ở đây.
erickson

6
@FunctionalInterface
public interface CheckedRunnable<E extends Exception> extends Runnable {

    @Override
    default void run() throws RuntimeException {
        try {
            runThrows();
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    void runThrows() throws E;

}

1

Một số người cố gắng thuyết phục bạn rằng bạn phải chơi đúng luật. Nghe, nhưng bạn có tuân theo hay không, bạn nên tự quyết định tùy thuộc vào hoàn cảnh của bạn. Thực tế là "bạn NÊN chơi theo luật" (không phải "bạn PHẢI chơi theo luật"). Chỉ cần lưu ý rằng nếu bạn không chơi đúng luật, có thể có hậu quả.

Tình huống này không chỉ áp dụng trong tình huống Runnable, mà với Java 8 cũng rất thường xuyên trong bối cảnh Luồng và những nơi khác mà các giao diện chức năng đã được giới thiệu mà không có khả năng xử lý các ngoại lệ đã kiểm tra. Ví dụ, Consumer, Supplier, Function, BiFunctionvà như vậy có tất cả được công bố mà không cần cơ sở vật chất để đối phó với trường hợp ngoại lệ kiểm tra.

Vậy các tình huống và lựa chọn là gì? Trong văn bản dưới đây, Runnablelà đại diện của bất kỳ giao diện chức năng nào không khai báo ngoại lệ hoặc tuyên bố ngoại lệ quá giới hạn cho trường hợp sử dụng hiện tại.

  1. Bạn đã tự khai báo Runnableở đâu đó và có thể thay thế Runnablebằng thứ khác.
    1. Xem xét thay thế Runnablebằng Callable<Void>. Về cơ bản là cùng một thứ, nhưng được phép ném các ngoại lệ; và return nullcuối cùng, đó là một sự khó chịu nhẹ.
    2. Cân nhắc thay thế Runnablebằng tùy chỉnh của riêng bạn @FunctionalInterfaceđể có thể loại bỏ chính xác những ngoại lệ mà bạn muốn.
  2. Bạn đã sử dụng một API và có sẵn các lựa chọn thay thế. Ví dụ: một số API Java bị quá tải nên bạn có thể sử dụng Callable<Void>thay thế Runnable.
  3. Bạn đã sử dụng một API và không có lựa chọn thay thế nào. Trong trường hợp đó, bạn vẫn chưa hết lựa chọn.
    1. Bạn có thể bọc ngoại lệ vào RuntimeException.
    2. Bạn có thể hack ngoại lệ vào RuntimeException bằng cách sử dụng một diễn viên không được kiểm tra.

Bạn có thể thử những cách sau. Đó là một chút hack, nhưng đôi khi hack là thứ chúng ta cần. Bởi vì, việc một ngoại lệ nên được kiểm tra hay bỏ chọn được xác định bởi kiểu của nó, nhưng trên thực tế, thực tế nên được xác định theo tình huống.

@FunctionalInterface
public interface ThrowingRunnable extends Runnable {
    @Override
    default void run() {
        try {
            tryRun();
        } catch (final Throwable t) {
            throwUnchecked(t);
        }
    }

    private static <E extends RuntimeException> void throwUnchecked(Throwable t) {
        throw (E) t;
    }

    void tryRun() throws Throwable;
}

Tôi thích điều này hơn new RuntimeException(t)vì nó có dấu vết ngăn xếp ngắn hơn.

Bây giờ bạn có thể làm:

executorService.submit((ThrowingRunnable) () -> {throw new Exception()});

Tuyên bố từ chối trách nhiệm: Khả năng thực hiện các phôi không được kiểm tra theo cách này có thể thực sự bị loại bỏ trong các phiên bản Java trong tương lai, khi thông tin kiểu generics được xử lý không chỉ tại thời điểm biên dịch mà còn trong thời gian chạy.


0

Yêu cầu của bạn không có ý nghĩa gì. Nếu bạn muốn thông báo cho chuỗi được gọi về một ngoại lệ đã xảy ra, bạn có thể làm điều đó thông qua cơ chế gọi lại. Điều này có thể thông qua Trình xử lý hoặc chương trình phát sóng hoặc bất cứ điều gì khác mà bạn có thể nghĩ ra.


0

Tôi nghĩ rằng một mẫu người nghe có thể giúp bạn trong trường hợp này. Trong trường hợp có một ngoại lệ xảy ra trong run()phương thức của bạn , hãy sử dụng khối try-catch và gửi một thông báo về một sự kiện ngoại lệ. Và sau đó xử lý sự kiện thông báo của bạn. Tôi nghĩ rằng đây sẽ là một cách tiếp cận rõ ràng hơn. Liên kết SO này cung cấp cho bạn một con trỏ hữu ích về hướng đó.


-1

Cách dễ nhất là xác định đối tượng ngoại lệ của riêng bạn, đối tượng này mở rộng RuntimeExceptionlớp thay vì Exceptionlớp.


Và làm thế nào bạn có thể nắm giữ RuntimeException này khi nó xảy ra, thưa ngài?
ngủ tập thể

Điều đó một mình không trả lời câu hỏi.
Karl Richter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.