Czy istnieje sposób, aby metoda run () Runnable rzucała wyjątek?

80

Metoda, którą wywołuję w run () w klasie, która implementuje Runnable ) jest przeznaczona do zgłaszania wyjątku.

Ale kompilator Java nie pozwala mi tego zrobić i sugeruje, żebym otoczył go try / catch.

Problem polega na tym, że otaczając go próbą / złapaniem, sprawiam, że ten konkretny run () staje się bezużyteczny. I nie chcą rzucać ten wyjątek.

Jeśli podam throwsdla samego run () , kompilator narzeka Exception is not compatible with throws clause in Runnable.run().

Zwykle jestem całkowicie w porządku, nie pozwalając run () rzucać wyjątku. Ale mam wyjątkową sytuację, w której muszę mieć taką funkcjonalność.

Jak obejść to ograniczenie?

Regex Rookie
źródło
Oprócz innych odpowiedzi, do śledzenia postępu zadania możesz skorzystać z klasy FutureTask.
JProgrammer
1
Pytanie o Javę bez Androida: stackoverflow.com/questions/1369204/ ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

26

Jeśli chcesz przekazać klasę, która implementuje Runnablesię do Threadframeworka, musisz postępować zgodnie z regułami tego frameworka, zobacz odpowiedź Ernesta Friedmana-Hilla, dlaczego zrobienie tego w inny sposób jest złym pomysłem.

Mam jednak przeczucie, że chcesz wywołać runmetodę bezpośrednio w kodzie, aby kod wywołujący mógł przetworzyć wyjątek.

Odpowiedź na ten problem jest prosta. Nie używaj Runnableinterfejsu z biblioteki Thread, ale stwórz własny interfejs ze zmodyfikowaną sygnaturą, która pozwala na wyrzucenie zaznaczonego wyjątku, np.

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

Możesz nawet utworzyć adapter, który konwertuje ten interfejs na rzeczywisty Runnable(poprzez obsługę zaznaczonego wyjątku) odpowiedni do użycia w środowisku Thread.

Alexander Pogrebnyak
źródło
Do takiego prostego rozwiązania doszło po prostu nie myśląc „w pudełku”. Oczywiście, Runnableto tylko prosty interfejs i możemy stworzyć własny. Nie jest to przydatne w przypadku użycia wątku, ale do przekazywania różnych „uruchamialnych” fragmentów kodu wokół tego jest idealne.
Richard Le Mesurier
82

Możesz użyć Callablezamiast tego, przesyłając go do an ExecutorServicei czekając na wynik z FutureTask.isDone()zwróconym przez ExecutorService.submit().

Kiedy isDone()zwraca prawdę, dzwonisz FutureTask.get(). Teraz, jeśli Callablewyrzuciłeś, a Exceptionnastępnie FutureTask.get()wyrzucisz Exceptionrównież, a oryginalny Wyjątek będziesz mógł uzyskać dostęp za pomocą Exception.getCause().

Alexander Kulyakhtin
źródło
23

run()Co by go złapało, gdyby rzucił zaznaczony wyjątek? Nie ma sposobu, aby zawrzeć to run()wywołanie w module obsługi, ponieważ nie piszesz kodu, który je wywołuje.

Możesz złapać sprawdzony wyjątek w run()metodzie i RuntimeExceptionw jego miejsce rzucić niezatwierdzony wyjątek (tj. ). Spowoduje to zakończenie wątku ze śladem stosu; może o to ci chodzi.

Jeśli zamiast tego chcesz, aby Twoja run()metoda zgłosiła gdzieś błąd, możesz po prostu podać metodę wywołania zwrotnego do wywołania bloku run()metody catch; ta metoda mogłaby gdzieś przechowywać obiekt wyjątku, a następnie zainteresowany wątek mógłby znaleźć obiekt w tej lokalizacji.

Ernest Friedman-Hill
źródło
2
Pierwsza część nie jest dobrym argumentem. „Jeśli main()zgłosiłby wyjątek zaznaczony, co by go złapało?” „Gdyby run()wyrzucił niesprawdzony wyjątek, co by go złapało?”
Christian Hujer
17

Tak, istnieje sposób na rzucenie zaznaczonego wyjątku od run()metody, ale jest tak okropny, że nie będę go udostępniać.

Oto, co możesz zrobić zamiast tego; używa tego samego mechanizmu, co wyjątek czasu wykonania:

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

Jak zauważyli inni, jeśli twoja run()metoda jest naprawdę celem a Thread, nie ma sensu rzucać wyjątku, ponieważ jest nieobserwowalny; zgłoszenie wyjątku ma taki sam efekt, jak nie zgłoszenie wyjątku (brak).

Jeśli to nie jest Threadcel, nie używaj Runnable. Na przykład może Callablelepiej pasuje.

erickson
źródło
Ale czy to powoduje awarię procesu, gdy rzuca?
Dinesh
@DineshVG Nie, tylko błąd w JVM może spowodować prawdziwą awarię. Domyślny program obsługi wyjątków po prostu drukuje wyjątek. Jeśli jesteś przyzwyczajony do tego, że proces kończy się później, to dlatego, że ten wątek był jedynym działającym wątkiem i został zakończony.
erickson,
Próbowałem (w przypadkach testowych oprzyrządowania Androida), używając tego do połączenia mydlanego, gdzie jeśli otrzymam 400 z połączenia Soap, rzucam wyjątek. To wywołanie mydła jest wywoływane z wątku podczas uruchamiania przypadku testowego. Ten wątek wykorzystuje to, t.getUncaughtExceptionHandler().uncaughtException(t, ex);aby przesłać go do przypadku testowego oprzyrządowania. Dodanie tej jednej linii powoduje awarię procesu !. Nie wiem dlaczego.
Dinesh
@DineshVG Czy w tym środowisku Thread.getDefaultUncaughtExceptionHandler()powraca null? Jeśli nie, jaki jest typ wyniku? Co się stanie, jeśli zamiast wywoływać uncaughtException(), zawiniesz zaznaczony wyjątek w a RuntimeExceptioni wyrzucisz to?
erickson,
1
@DineshVG Android może ustawiać domyślną procedurę obsługi nieprzechwyconych wyjątków, która to robi. Dlatego zapytałem, czy Thread.getDefaultUncaughtExceptionHandler()wraca null; jeśli nie, Android zapewnia ustawienie domyślne, aby zaoferować raportowanie itp. Ale możesz ustawić to, co chcesz. Więcej informacji znajdziesz tutaj.
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;

}
Sina Madani
źródło
1

Niektórzy próbują cię przekonać, że musisz grać według zasad. Posłuchaj, ale czy jesteś posłuszny, powinieneś zdecydować sam w zależności od sytuacji. Rzeczywistość jest taka, że ​​„POWINIENEŚ grać według zasad” (a nie „MUSISZ grać według zasad”). Pamiętaj tylko, że jeśli nie będziesz postępować zgodnie z zasadami, może to mieć konsekwencje.

Sytuacja dotyczy nie tylko sytuacji Runnable, ale z Javą 8 również bardzo często w kontekście Strumieni i innych miejsc, w których wprowadzono funkcjonalne interfejsy bez możliwości radzenia sobie z zaznaczonymi wyjątkami. Na przykład Consumer, Supplier, Function, BiFunctioni tak dalej, wszystkie zostały uznane bez urządzeń do czynienia ze sprawdzonych wyjątków.

Więc jakie są sytuacje i opcje? W poniższym tekście Runnablejest reprezentatywny dla każdego funkcjonalnego interfejsu, który nie deklaruje wyjątków lub deklaruje wyjątki zbyt ograniczone do danego przypadku użycia.

  1. RunnableSam się gdzieś zadeklarowałeś i możesz zamienić na Runnablecoś innego.
    1. Rozważyć wymianę Runnablez Callable<Void>. Zasadniczo to samo, ale można zgłaszać wyjątki; i musi return nullw końcu, co jest łagodną irytacją.
    2. Rozważ zastąpienie Runnablewłasnym niestandardowym, @FunctionalInterfacektóry może generować dokładnie te wyjątki, które chcesz.
  2. Użyłeś interfejsu API i dostępne są alternatywy. Na przykład niektóre interfejsy API języka Java są przeciążone, więc można użyć Callable<Void>zamiast Runnable.
  3. Użyłeś API i nie ma alternatywy. W takim przypadku nadal nie brakuje Ci opcji.
    1. Możesz zawinąć wyjątek w RuntimeException.
    2. Możesz zhakować wyjątek do RuntimeException, używając niesprawdzonego rzutowania.

Możesz spróbować następujących rzeczy. To trochę hackowanie, ale czasami potrzebujemy włamania. Ponieważ to, czy wyjątek powinien być zaznaczony, czy odznaczony, jest określane przez jego typ, ale praktycznie powinno być definiowane przez sytuację.

@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;
}

Wolę to bardziej, new RuntimeException(t)ponieważ ma krótszy ślad stosu.

Możesz teraz:

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

Zastrzeżenie: Możliwość wykonywania niesprawdzonych rzutów w ten sposób może w rzeczywistości zostać usunięta w przyszłych wersjach języka Java, gdy informacje o typach ogólnych są przetwarzane nie tylko w czasie kompilacji, ale także w czasie wykonywania.

Christian Hujer
źródło
0

Twoje wymaganie nie ma sensu. Jeśli chcesz powiadomić wywoływanego wątku o zdarzeniu wyjątku, możesz to zrobić za pomocą mechanizmu wywołania zwrotnego. Może to nastąpić za pośrednictwem Handlera, transmisji lub czegokolwiek innego, o czym możesz pomyśleć.

Kumar Bibek
źródło
0

Myślę, że wzorzec słuchacza może ci pomóc w tym scenariuszu. W przypadku wystąpienia wyjątku w Twojej run()metodzie, użyj bloku try-catch iw catch wyślij powiadomienie o zdarzeniu wyjątku. A następnie obsłuż zdarzenie powiadomienia. Myślę, że byłoby to czystsze podejście. Ten odsyłacz SO zawiera pomocne wskazówki w tym kierunku.

Sujay
źródło
-1

Najłatwiejszym sposobem jest zdefiniowanie własnego obiektu wyjątku, który rozszerza RuntimeExceptionklasę zamiast Exceptionklasy.

joas
źródło
Jak można zdobyć ten wyjątek RuntimeException, gdy wystąpi, mój drogi panie?
Dormouse
Samo to nie daje odpowiedzi na pytanie.
Karl Richter