Rückkehr von einem finally-Block in Java

176

Ich war kürzlich überrascht, dass es möglich ist, eine return-Anweisung in einem finally-Block in Java zu haben.

Es scheint, als ob viele Leute denken, dass es eine schlechte Sache ist, wie in " Nicht in einer finally-Klausel zurückkehren " beschrieben. Als ich etwas tiefer kratzte, fand ich auch " Javas Rückkehr ist nicht immer ", was einige ziemlich schreckliche Beispiele für andere Arten der Flusskontrolle in endgültigen Blöcken zeigt.

Meine Frage ist also, kann mir jemand ein Beispiel geben, bei dem eine return-Anweisung (oder eine andere Flusskontrolle) in einem finally-Block besseren / besser lesbaren Code erzeugt?

Matt Sheppard
quelle

Antworten:

89

Die Beispiele, die Sie angegeben haben, sind Grund genug, die Flusskontrolle ab schließlich nicht mehr zu verwenden.

Selbst wenn es ein erfundenes Beispiel gibt, in dem es "besser" ist, sollten Sie den Entwickler in Betracht ziehen, der Ihren Code später warten muss und der die Feinheiten möglicherweise nicht kennt. Dieser arme Entwickler könnte sogar Sie sein ...

Jason Cohen
quelle
5
Sicher. Ich frage wohl, ob mir jemand ein wirklich überzeugendes Beispiel auf der Seite des Guten geben kann.
Matt Sheppard
@ MattSheppard in daos, ich werde oft den Ausgang einer Abfrage in einem try-finally
Blake
147

Ich hatte es vor Jahren WIRKLICH schwer, einen Fehler aufzuspüren, der dadurch verursacht wurde. Der Code war so etwas wie:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}

Was passiert ist, ist, dass die Ausnahme in einem anderen Code abgeworfen wurde. Es wurde innerhalb der somethingThatThrewAnException()Methode gefangen und protokolliert und erneut geworfen . Aber die Ausnahme wurde nicht in der Vergangenheit verbreitet problemMethod(). Nach einer langen Zeit, in der wir uns das angesehen haben, haben wir es endlich bis zur Rückgabemethode aufgespürt. Die Rückgabemethode im finally-Block hat im Grunde genommen verhindert, dass sich die im try-Block aufgetretene Ausnahme ausbreitet, obwohl sie nicht abgefangen wurde.

Wie andere gesagt haben, ist es zwar legal, von einem endgültigen Block gemäß der Java-Spezifikation zurückzukehren, aber es ist eine SCHLECHTE Sache und sollte nicht getan werden.

John Meagher
quelle
Wo soll man dann die Rückgabe hinstellen?
Parsecer
@parsecer Ich würde gleich nach dem Aufruf von SomethingThatThrewAnException () im try-Block sagen
Tiago Sippert
@ Parsecer, ?? Mach es einfach wie gewohnt, nach dem Endlich.
Pacerier
21

javac warnt vor der endgültigen Rückgabe, wenn Sie -Xlint: finally verwenden. Ursprünglich gab javac keine Warnungen aus - wenn etwas mit dem Code nicht stimmt, sollte er nicht kompiliert werden können. Leider bedeutet Abwärtskompatibilität, dass unerwartete geniale Dummheit nicht verboten werden kann.

Ausnahmen können von endgültigen Blöcken geworfen werden, aber in diesem Fall ist das gezeigte Verhalten mit ziemlicher Sicherheit das, was Sie wollen.

Tom Hawtin - Tackline
quelle
13

Das Hinzufügen von Kontrollstrukturen und die Rückkehr zu finally {} -Blöcken sind nur ein weiteres Beispiel für Missbräuche, die in praktisch allen Entwicklungssprachen verstreut sind. Jason hat zu Recht vorgeschlagen, dass es leicht zu einem Alptraum für die Instandhaltung werden könnte - die Argumente gegen vorzeitige Rückgaben von Funktionen gelten eher für diesen Fall von "verspäteten Rückgaben".

Schließlich gibt es Blöcke für einen Zweck, damit Sie vollständig aufräumen können, unabhängig davon, was im gesamten vorhergehenden Code passiert ist. Im Prinzip geht es darum, Dateizeiger, Datenbankverbindungen usw. zu schließen / freizugeben, obwohl ich sehen konnte, dass es gestreckt ist, das Hinzufügen einer maßgeschneiderten Prüfung zu sagen.

Alles, was die Rückgabe der Funktion beeinflusst, sollte im try {} -Block liegen. Selbst wenn Sie eine Methode hätten, mit der Sie einen externen Status überprüft, eine zeitaufwändige Operation ausgeführt und diesen Status dann erneut überprüft haben, falls er ungültig wird, möchten Sie dennoch die zweite Überprüfung im try {} - wenn sie sich schließlich im Inneren befindet {} Wenn die lange Operation fehlgeschlagen ist, würden Sie diesen Status ein zweites Mal unnötig überprüfen.

Ian
quelle
6

Ein einfacher Groovy-Test:

public class Instance {

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) {

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try {
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
    } catch (Exception ex) {
      println "execute threw exception: " + ex.getMessage()
    }  
    println "--------------------------------------------------------------------------\n"

  }

  String execute(boolean returnInFinally, boolean throwError) {
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try {
        if (throwError) {
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        }
        println "...return 'OK' from execute"
        return "OK"
      } finally {
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      }
  }
}

Instance instance = new Instance()
instance.test(false)
instance.test(true)

Ausgabe:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

Frage:

Ein interessanter Punkt für mich war zu sehen, wie Groovy mit impliziten Renditen umgeht. In Groovy ist es möglich, von einer Methode "zurückzukehren", indem einfach ein Wert am Ende verbleibt (ohne Rückgabe). Was denkst du passiert, wenn du das auskommentierst? Zeile runningThreads.remove (..) in der finally-Anweisung auskommentieren - wird dadurch der reguläre Rückgabewert ("OK") überschrieben und die Ausnahme abgedeckt?!

Prof. Ondino
quelle
0

Wenn Sie aus einem finallyBlock zurückkehren, geht exceptionsdies verloren.

Eine return-Anweisung in einem finally-Block führt dazu, dass alle Ausnahmen, die möglicherweise im try- oder catch-Block ausgelöst werden, verworfen werden.

Gemäß der Java-Sprachspezifikation:

Wenn die Ausführung des try-Blocks aus einem anderen Grund R abrupt abgeschlossen wird, wird der finally-Block ausgeführt, und es gibt eine Auswahl:

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

Hinweis: Gemäß JLS 14.17 wird eine return-Anweisung immer abrupt abgeschlossen.

Ankur Lathi
quelle