Gibt es eine Möglichkeit, Runnables run () zu einer Ausnahme zu machen?

80

Eine Methode, die ich in run () in einer Klasse aufrufe , die Runnable implementiert , soll eine Ausnahme auslösen .

Aber der Java-Compiler lässt mich das nicht und schlägt vor, ihn mit try / catch zu umgeben.

Das Problem ist, dass ich durch das Umgeben mit einem Versuch / Fang diesen bestimmten run () unbrauchbar mache . Ich tun will diese Ausnahme werfen.

Wenn ich throwsfür run () selbst spezifiziere , beschwert sich der Compiler darüber Exception is not compatible with throws clause in Runnable.run().

Normalerweise bin ich völlig in Ordnung, wenn ich nicht zulasse , dass run () eine Ausnahme auslöst. Aber ich habe eine einzigartige Situation, in der ich diese Funktionalität haben muss.

Wie kann ich diese Einschränkung umgehen?

Regex Rookie
quelle
Zusätzlich zu anderen Antworten können Sie die FutureTask-Klasse verwenden, um den Fortschritt der Aufgabe zu verfolgen.
JProgrammer
1
Nicht-Android Java Frage: stackoverflow.com/questions/1369204/…
Ciro Santilli 法轮功 冠状 病 六四 事件 20

Antworten:

23

Wenn Sie eine Klasse übergeben möchten, die Runnablein das ThreadFramework implementiert wird , müssen Sie sich an die Regeln dieses Frameworks halten. Lesen Sie die Antwort von Ernest Friedman-Hill, warum es eine schlechte Idee ist, dies anders zu tun.

Ich habe jedoch die Vermutung, dass Sie die runMethode direkt in Ihrem Code aufrufen möchten , damit Ihr aufrufender Code die Ausnahme verarbeiten kann.

Die Antwort auf dieses Problem ist einfach. Verwenden Sie keine RunnableSchnittstelle aus der Thread-Bibliothek, sondern erstellen Sie eine eigene Schnittstelle mit der geänderten Signatur, mit der die aktivierte Ausnahme ausgelöst werden kann, z

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

Sie können sogar einen Adapter erstellen, der diese Schnittstelle in real konvertiert Runnable(indem die aktivierte Ausnahme behandelt wird) und für die Verwendung im Thread-Framework geeignet ist.

Alexander Pogrebnyak
quelle
Eine so einfache Lösung kam nur dadurch zustande, dass man nicht "in der Box" dachte. Natürlich Runnableist a nur eine einfache Schnittstelle und wir können unsere eigene erstellen. Nicht nützlich für den Anwendungsfall des Threads, aber zum Übergeben verschiedener "ausführbarer" Codestücke ist dies perfekt.
Richard Le Mesurier
82

Sie können Callablestattdessen ein verwenden, es an ein senden ExecutorServiceund auf das Ergebnis warten, das FutureTask.isDone()vom zurückgegeben wird ExecutorService.submit().

Wenn isDone()true zurückgegeben wird, rufen Sie an FutureTask.get(). Nun, wenn Ihr Callablegeworfen hat eine Exceptiondann FutureTask.get()wiill ein Wurf Exceptionzu und die ursprüngliche Exception können Sie für den Zugriff verwenden Exception.getCause().

Alexander Kulyakhtin
quelle
23

Wenn run()eine aktivierte Ausnahme ausgelöst würde, was würde sie abfangen? Sie können diesen run()Aufruf nicht in einen Handler einschließen , da Sie den Code, der ihn aufruft, nicht schreiben.

Sie können Ihre run()aktivierte Ausnahme in der Methode abfangen und RuntimeExceptionan ihrer Stelle eine nicht überprüfte Ausnahme (dh ) auslösen . Dadurch wird der Thread mit einem Stack-Trace beendet. Vielleicht ist es das, wonach du suchst.

Wenn Sie stattdessen Ihre wollen run()Methode den Fehler irgendwo melden, dann können Sie nur eine Callback - Methode für das zur Verfügung stellen run()des Verfahrens catchBlock zu nennen; Diese Methode könnte das Ausnahmeobjekt irgendwo speichern, und dann könnte Ihr interessierter Thread das Objekt an dieser Stelle finden.

Ernest Friedman-Hill
quelle
2
Der erste Teil ist kein gutes Argument. "Wenn main()eine aktivierte Ausnahme ausgelöst würde, was würde sie abfangen?" "Wenn run()eine ungeprüfte Ausnahme ausgelöst würde, was würde sie fangen?"
Christian Hujer
16

Ja, es gibt eine Möglichkeit, eine aktivierte Ausnahme von der run()Methode auszulösen, aber es ist so schrecklich, dass ich sie nicht freigeben werde.

Folgendes können Sie stattdessen tun: Es verwendet denselben Mechanismus, den eine Laufzeitausnahme ausführen würde:

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

Wie andere angemerkt haben , macht es keinen Sinn, eine Ausnahme auszulösen, wenn Ihre run()Methode wirklich das Ziel von a ist Thread, da sie nicht beobachtbar ist. Das Auslösen einer Ausnahme hat den gleichen Effekt wie das Nichtauslösen einer Ausnahme (keine).

Wenn es kein ThreadZiel ist, verwenden Sie es nicht Runnable. Zum Beispiel Callablepasst vielleicht besser.

erickson
quelle
Aber führt dies dazu, dass der Prozess abstürzt, wenn er ausgelöst wird?
Dinesh
@DineshVG Nein, nur ein Fehler in der JVM kann einen echten Absturz verursachen. Der Standard-Ausnahmebehandler druckt nur die Ausnahme. Wenn Sie es gewohnt sind, dass der Prozess danach beendet wird, liegt dies daran, dass dieser Thread als einziger Thread ausgeführt wurde und beendet wurde.
Erickson
Ich habe versucht (in Testfällen für Android-Instrumente), dies für einen Seifenanruf zu verwenden. Wenn ich einen 400 von Soap-Anruf erhalte, werde ich eine Ausnahme auslösen. Dieser Seifenaufruf wird von einem Thread aufgerufen, während der Testfall gestartet wird. Dieser Thread verwendet dies, t.getUncaughtExceptionHandler().uncaughtException(t, ex);um es in den Instrumentierungstestfall zu werfen. Das Hinzufügen dieser einen Zeile führt zum Absturz des Prozesses!. Ich weiß nicht warum.
Dinesh
@DineshVG Gibt in dieser Umgebung Thread.getDefaultUncaughtExceptionHandler()zurück null? Wenn nicht, wie lautet die Art des Ergebnisses? Was passiert, wenn Sie anstelle eines Aufrufs uncaughtException()die aktivierte Ausnahme in a RuntimeExceptioneinschließen und diese auslösen?
Erickson
1
@DineshVG Android legt möglicherweise einen Standard-Handler für nicht erfasste Ausnahmen fest, der dies tut. Deshalb habe ich gefragt, ob ich Thread.getDefaultUncaughtExceptionHandler()zurückkomme null. Wenn nicht, stellt Android einen Standard bereit, um Berichte usw. anzubieten. Sie können ihn jedoch so einstellen, dass er das tut, was Sie wollen. Hier gibt es mehr Infos .
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
quelle
1

Einige Leute versuchen dich davon zu überzeugen, dass du dich an die Regeln halten musst. Hören Sie zu, aber ob Sie gehorchen, sollten Sie selbst entscheiden, abhängig von Ihrer Situation. Die Realität ist "Sie sollten nach den Regeln spielen" (nicht "Sie müssen nach den Regeln spielen"). Seien Sie sich nur bewusst, dass es Konsequenzen haben kann, wenn Sie sich nicht an die Regeln halten.

Die Situation gilt nicht nur in der Situation von Runnable, sondern mit Java 8 auch sehr häufig im Zusammenhang mit Streams und anderen Orten, an denen funktionale Schnittstellen eingeführt wurden, ohne die Möglichkeit, geprüfte Ausnahmen zu behandeln. Zum Beispiel Consumer, Supplier, Function, BiFunctionund haben so auf alle ohne Einrichtungen erklärt mit geprüften Ausnahmen zu behandeln.

Was sind die Situationen und Optionen? Im folgenden Text Runnablesteht er für jede Funktionsschnittstelle, die keine Ausnahmen deklariert oder Ausnahmen für den jeweiligen Anwendungsfall zu eingeschränkt deklariert.

  1. Sie haben Runnableselbst irgendwo deklariert und könnten durch Runnableetwas anderes ersetzen .
    1. Betrachten Sie ersetzen Runnablemit Callable<Void>. Grundsätzlich das Gleiche, darf aber Ausnahmen werfen; und muss return nullam Ende, was ein leichter Ärger ist.
    2. Erwägen Sie, Runnabledurch Ihre eigene Gewohnheit zu ersetzen @FunctionalInterface, die genau die gewünschten Ausnahmen auslösen kann.
  2. Sie haben eine API verwendet und es stehen Alternativen zur Verfügung. Beispielsweise sind einige Java-APIs überladen, sodass Sie Callable<Void>stattdessen verwenden können Runnable.
  3. Sie haben eine API verwendet und es gibt keine Alternativen. In diesem Fall stehen Ihnen immer noch keine Optionen zur Verfügung.
    1. Sie können die Ausnahme einschließen RuntimeException.
    2. Sie können die Ausnahme in eine RuntimeException hacken, indem Sie eine nicht aktivierte Besetzung verwenden.

Sie können Folgendes versuchen. Es ist ein bisschen ein Hack, aber manchmal ist ein Hack das, was wir brauchen. Denn ob eine Ausnahme aktiviert oder deaktiviert werden soll, hängt von ihrem Typ ab, sollte aber praktisch von der Situation abhängen.

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

Ich bevorzuge dies, new RuntimeException(t)weil es eine kürzere Stapelspur hat.

Sie können jetzt tun:

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

Haftungsausschluss: Die Möglichkeit, ungeprüfte Casts auf diese Weise durchzuführen, wird in zukünftigen Java-Versionen möglicherweise tatsächlich entfernt, wenn Informationen zum Generika-Typ nicht nur zur Kompilierungszeit, sondern auch zur Laufzeit verarbeitet werden.

Christian Hujer
quelle
0

Ihre Anforderung macht keinen Sinn. Wenn Sie den aufgerufenen Thread über eine aufgetretene Ausnahme informieren möchten, können Sie dies über einen Rückrufmechanismus tun. Dies kann über einen Handler oder eine Sendung geschehen oder was auch immer Sie sich sonst noch vorstellen können.

Kumar Bibek
quelle
0

Ich denke, ein Hörermuster könnte Ihnen bei diesem Szenario helfen. Wenn in Ihrer run()Methode eine Ausnahme auftritt , verwenden Sie einen Try-Catch-Block und senden Sie im Catch eine Benachrichtigung über ein Ausnahmeereignis. Und dann behandeln Sie Ihr Benachrichtigungsereignis. Ich denke, das wäre ein sauberer Ansatz. Dieser SO-Link gibt Ihnen einen hilfreichen Hinweis auf diese Richtung.

Sujay
quelle
-1

Am einfachsten ist es, ein eigenes Ausnahmeobjekt zu definieren, das die RuntimeExceptionKlasse anstelle der ExceptionKlasse erweitert.

Joas
quelle
Und wie würden Sie diese RuntimeException erreichen, wenn sie auftritt, mein lieber Herr?
Siebenschläfer
Das allein beantwortet die Frage nicht.
Karl Richter