Повернення з остаточно блоку на Java


177

Нещодавно я був здивований, коли виявив, що в Java можливо остаточно повернути заяву.

Здається, що багато людей думають, що це погано робити, як описано у статті " Не повертайся нарешті ". Трохи заглибившись, я також виявив, що « повернення Java не завжди є », що показує кілька жахливих прикладів інших типів управління потоком, нарешті, блокує.

Отже, моє запитання полягає в тому, чи може хто-небудь надати мені приклад, коли заява про повернення (або інший контроль потоку) в остаточному блоці створює кращий / більш читабельний код?

Відповіді:


90

Наведені вами приклади є достатньою причиною, щоб нарешті не використовувати контроль управління.

Навіть якщо є надуманий приклад, коли це "краще", розгляньте розробника, який повинен підтримувати ваш код пізніше і який може не знати про тонкощі. Цей бідний розробник може бути навіть ти ...


5
Звичайно. Я думаю, я прошу, якщо хтось може надати мені справді переконливий приклад на користь.
Метт Шеппард

@MattSheppard в DAOS, я буду часто реєструвати вихід запиту в примірки , нарешті
Blake

148

Мені було ДУЖЕ важко відстежувати помилку років тому, яка була викликана цим. Код був приблизно таким:

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

Сталося те, що виняток було закинуто в якийсь інший код. Його вловлювали, записували в журнал і перекидали в рамках somethingThatThrewAnException()методу. Але виняток не розповсюджувався в минулому problemMethod(). Після довгого часу дивлячись на це, ми нарешті простежили це до методу повернення. Метод повернення в остаточному блоці в основному зупиняв виняток, що трапився в пробному блоці, від розповсюдження, навіть якщо він не був спійманий.

Як казали інші, хоча законно повертатися з остаточного блоку відповідно до специфікації Java, це BAD річ і цього не слід робити.


Куди слід поставити повернення?
розбір

@parsecer Я б сказав відразу після того, як зателефонував щось у тетThrewAnException () всередині
пробного

@parsecer, ?? Просто зробіть це звичайним способом, нарешті.
Pacerier

21

javac попередить про повернення нарешті, якщо використовувати -Xlint: нарешті. Спочатку javac не отримував жодних попереджень - якщо щось не в порядку з кодом, його не вдасться компілювати. На жаль, зворотна сумісність означає, що непередбачена геніальна дурість не може бути заборонена.

Винятки можуть бути викинуті з остаточних блоків, але в цьому випадку проявляється поведінка майже напевно те, що ви хочете.


13

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

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

Все, що впливає на повернення функції, має лежати у блоці 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 (..) в остаточному звіті - чи це перезаписає звичайне значення повернення ("ОК") і покриє виняток ?!


0

Повернення зсередини finallyблоку призведе exceptionsдо втрати.

Оператор повернення всередині остаточного блоку призведе до відмови будь-якого винятку, який може бути кинутий у блок спробу або спіймання.

Відповідно до специфікації мови Java:

Якщо виконання блоку спробу завершено різко з будь-якої іншої причини 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).

Примітка: Згідно з JLS 14.17 - виписка про повернення завжди різко завершується.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.