Возвращаясь из блока finally в Java

177

Недавно я был удивлен, обнаружив, что в блоке finally в Java возможно иметь оператор return.

Похоже, что многие люди думают, что это плохо, как описано в « Не возвращайся в конце предложения ». Немного углубившись в подробности, я также обнаружил, что «возвращение Java не всегда » показывает довольно ужасные примеры других типов управления потоками в блоках finally.

Итак, мой вопрос: может ли кто-нибудь привести мне пример, когда оператор return (или другой элемент управления потоком) в блоке finally создает лучший / более читаемый код?

Мэтт Шеппард
источник

Ответы:

90

Приведенные вами примеры являются достаточной причиной, чтобы не использовать управление потоком от finally.

Даже если есть надуманный пример, где «лучше», подумайте о разработчике, который должен поддерживать ваш код позже и который может не знать о тонкостях. Этот бедный разработчик может быть даже вы ....

Джейсон Коэн
источник
5
Конечно. Наверное, я спрашиваю, может ли кто-нибудь дать мне действительно убедительный пример на стороне добра.
Мэтт Шеппард
@MattSheppard в Даосе, я буду часто регистрировать выход запроса в конце попытки
Blake
148

У меня было ДЕЙСТВИТЕЛЬНО трудное время, чтобы отследить ошибку, которая была вызвана этим. Код был что-то вроде:

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

Случилось так, что исключение было сгенерировано в каком-то другом коде. Он был пойман и зарегистрирован и переброшен в рамках somethingThatThrewAnException()метода. Но исключение не распространялось в прошлом problemMethod(). После долгого времени просмотра мы наконец отследили его до метода return. Метод return в блоке finally в основном останавливал распространение исключения, которое произошло в блоке try, даже если оно не было перехвачено.

Как уже говорили другие, хотя и законно возвращаться из блока finally в соответствии со спецификацией Java, это ПЛОХАЯ вещь, и ее не следует делать.

Джон Мигер
источник
Куда же тогда положить возврат?
парсер
@parsecer Я бы сказал, что сразу после вызова что-тоThatThrewAnException () внутри блока try
Tiago Sippert
@parsecer, ?? Просто сделай это обычным способом, после, наконец.
Пейсер
21

Javac предупредит о возвращении в конце, если вы используете -Xlint: наконец. Первоначально javac не выдавал предупреждений - если что-то не так с кодом, он не должен компилироваться. К сожалению, обратная совместимость означает, что непредвиденная глупость не может быть запрещена.

Исключения могут быть выброшены из блоков finally, но в этом случае демонстрируемое поведение почти наверняка соответствует вашим ожиданиям.

Том Хотин - Tackline
источник
13

Добавление управляющих структур и возврат к блокам finally {} - это еще один пример злоупотреблений «просто потому, что вы можете», которые распространены практически на всех языках разработки. Джейсон был прав, предполагая, что он легко может стать кошмаром обслуживания - аргументы против раннего возврата из функций более применимы к этому случаю «позднего возврата».

Наконец, существуют блоки для одной цели, чтобы вы могли полностью привести себя в порядок, независимо от того, что произошло во всем предыдущем коде. В основном это закрытие / освобождение файловых указателей, соединений с базой данных и т. Д., Хотя я мог видеть, что это растягивается, чтобы добавить добавление в сделанный на заказ аудит.

Все, что влияет на возврат функции, должно лежать в блоке try {}. Даже если у вас есть метод, с помощью которого вы проверяете внешнее состояние, выполняете трудоемкую операцию, а затем снова проверяете это состояние в случае, если оно становится недействительным, вы все равно хотели бы провести вторую проверку внутри попытки {} - если бы она находилась внутри, наконец, {} и длительная операция завершилась неудачно, вы бы без необходимости проверяли это состояние во второй раз.

Ян
источник
6

Простой 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)

Вывод:

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

Вопрос:

Одним интересным моментом для меня было посмотреть, как Groovy справляется с неявными доходами. В Groovy можно «вернуться» из метода, просто оставив значение в конце (без возврата). Как вы думаете, что произойдет, если вы раскомментируете строку runningThreads.remove (..) в операторе finally - это перезапишет обычное возвращаемое значение («ОК») и охватит исключение ?!

Профессор Ондино
источник
0

Возвращение изнутри finallyблока приведет exceptionsк потере.

Оператор return внутри блока finally приведет к тому, что любое исключение, которое может быть сгенерировано в блоке try или catch, будет отброшено.

Согласно спецификации языка Java:

Если выполнение блока try завершается внезапно по какой-либо другой причине R, выполняется блок finally, и затем появляется выбор:

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

Примечание. Согласно JLS 14.17 оператор возврата всегда завершается преждевременно.

Анкур Лати
источник