Wracając z ostatniego bloku w Javie

177

Byłem ostatnio zaskoczony, gdy odkryłem, że możliwe jest posiadanie instrukcji return w końcowym bloku w Javie.

Wygląda na to, że wiele osób uważa, że ​​zrobienie czegoś złego, jak opisano w sekcjiNie wracaj w klauzuli na końcu ”. Zagłębiając się nieco głębiej, znalazłem również „ zwrot Javy nie zawsze ”, który pokazuje całkiem okropne przykłady innych typów kontroli przepływu w ostatecznie blokach.

Tak więc moje pytanie brzmi: czy ktoś może mi podać przykład, w którym instrukcja return (lub inna kontrola przepływu) w bloku Final tworzy lepszy / bardziej czytelny kod?

Matt Sheppard
źródło

Odpowiedzi:

90

Podane przykłady są wystarczającym powodem, aby ostatecznie nie używać kontroli przepływu.

Nawet jeśli istnieje wymyślony przykład, w którym jest „lepiej”, weź pod uwagę programistę, który musi później utrzymywać Twój kod i który może nie być świadomy jego subtelności. Tym biednym programistą możesz być nawet ty ....

Jason Cohen
źródło
5
Pewnie. Myślę, że pytam na wypadek, gdyby ktoś mógł mi podać jakiś naprawdę przekonujący przykład dotyczący dobra.
Matt Sheppard,
@MattSheppard w daos, często będę logować zakończenie zapytania w końcowej próbie
Blake
148

Miałem NAPRAWDĘ trudny czas, aby wyśledzić błąd lata temu, który był przez to spowodowany. Kod wyglądał mniej więcej tak:

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

Zdarzyło się, że wyjątek został odrzucony w innym kodzie. Został złapany, zarejestrowany i ponownie wyrzucony w ramach somethingThatThrewAnException()metody. Ale wyjątek nie był propagowany w przeszłości problemMethod(). Po długim czasie przyglądania się temu w końcu wyśledziliśmy to do metody zwrotu. Metoda powrotu w bloku final w zasadzie zatrzymywała propagację wyjątku, który wystąpił w bloku try, mimo że nie został przechwycony.

Jak powiedzieli inni, chociaż powrót z końcowego bloku jest legalny zgodnie ze specyfikacją Java, jest to ZŁA rzecz i nie należy tego robić.

John Meagher
źródło
Gdzie w takim razie umieścić zwrot?
parsecer
@parsecer powiedziałbym zaraz po wywołaniu somethingThatThrewAnException () wewnątrz bloku try
Tiago Sippert
@parsecer, ?? Po prostu zrób to w zwykły sposób, po zakończeniu.
Pacerier
21

javac ostrzeże o powrocie w końcu, jeśli użyjesz -Xlint: w końcu. Pierwotnie javac nie emitował żadnych ostrzeżeń - jeśli coś jest nie tak z kodem, kompilacja powinna się nie powieść. Niestety kompatybilność wsteczna oznacza, że ​​nie można zabronić nieoczekiwanej genialnej głupoty.

Wyjątki mogą być wyrzucane z ostatecznych bloków, ale w tym przypadku wykazywane zachowanie jest prawie na pewno tym, czego chcesz.

Tom Hawtin - haczyk
źródło
13

Dodawanie struktur kontrolnych i powrót do bloków w końcu {} to kolejny przykład nadużyć typu „tylko dlatego, że można”, które są rozproszone po praktycznie wszystkich językach programowania. Jason miał rację, sugerując, że może to łatwo stać się koszmarem związanym z konserwacją - argumenty przeciwko wczesnym zwrotom z funkcji odnoszą się bardziej do tego przypadku „późnych zwrotów”.

W końcu bloki istnieją w jednym celu, abyś mógł całkowicie uporządkować siebie, bez względu na to, co wydarzyło się w całym poprzednim kodzie. Zasadniczo jest to zamykanie / zwalnianie wskaźników do plików, połączeń z bazą danych itp., Chociaż widziałem, że jest to rozciągane, aby powiedzieć, że dodaje się audyt na zamówienie.

Wszystko, co wpływa na powrót funkcji, powinno znajdować się w bloku try {}. Nawet gdybyś miał metodę, za pomocą której sprawdziłeś stan zewnętrzny, wykonałeś czasochłonną operację, a następnie sprawdziłeś ten stan ponownie na wypadek, gdyby stał się nieważny, nadal chciałbyś, aby drugie sprawdzenie w try {} - jeśli w końcu siedział w środku {} a długa operacja nie powiodła się, wtedy sprawdzałbyś ten stan po raz drugi niepotrzebnie.

Ian
źródło
6

Prosty test Groovy:

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)

Wynik:

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 ^^
-----------------------------------------------------------------------------

Pytanie:

Ciekawym punktem dla mnie było zobaczenie, jak Groovy radzi sobie z niejawnymi zwrotami. W Groovy możliwe jest „zwrócenie” metody po prostu pozostawienie wartości na końcu (bez powrotu). Jak myślisz, co się stanie, jeśli odkomentujesz linię runningThreads.remove (..) w instrukcji last - czy spowoduje to nadpisanie zwykłej zwracanej wartości („OK”) i zakrycie wyjątku ?!

Prof. Ondino
źródło
0

Powrót z wnętrza finallybloku spowoduje exceptionszagubienie.

Instrukcja return wewnątrz bloku finala spowoduje odrzucenie każdego wyjątku, który może zostać zgłoszony w bloku try lub catch.

Zgodnie ze specyfikacją języka Java:

Jeśli wykonanie bloku try kończy się nagle z innego powodu R, wykonywany jest ostatni blok, a następnie jest wybór:

   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).

Uwaga: zgodnie z JLS 14.17 - instrukcja zwrotu zawsze kończy się nagle.

Ankur Lathi
źródło