Volviendo de un bloque finalmente en Java

177

Recientemente me sorprendió descubrir que es posible tener una declaración de devolución en un bloque finalmente en Java.

Parece que mucha gente piensa que es algo malo hacer como se describe en ' No regrese en una cláusula final '. Rascando un poco más, también encontré ' El retorno de Java no siempre ', que muestra algunos ejemplos bastante horribles de otros tipos de control de flujo en bloques finalmente.

Entonces, mi pregunta es, ¿alguien puede darme un ejemplo donde una declaración de retorno (u otro control de flujo) en un bloque finalmente produzca un código mejor / más legible?

Matt Sheppard
fuente

Respuestas:

90

Los ejemplos que proporcionó son motivo suficiente para no utilizar finalmente el control de flujo.

Incluso si hay un ejemplo artificial en el que es "mejor", considere el desarrollador que tiene que mantener su código más adelante y que podría no ser consciente de las sutilezas. Ese pobre desarrollador podría ser incluso tú ...

Jason Cohen
fuente
55
Por supuesto. Supongo que estoy preguntando en caso de que alguien pueda darme un ejemplo realmente convincente del lado del bien.
Matt Sheppard
@MattSheppard en daos, a menudo registraré la salida de una consulta en un intento de prueba
Blake
148

Hace muchos años tuve dificultades para localizar un error que fue causado por esto. El código era algo así como:

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

Lo que sucedió es que la excepción se arrojó en algún otro código. Fue capturado y registrado y vuelto a lanzar dentro del somethingThatThrewAnException()método. Pero la excepción no se propagaba al pasado problemMethod(). Después de MUCHO tiempo de mirar esto, finalmente lo rastreamos hasta el método de retorno. El método de retorno en el bloque finalmente básicamente estaba deteniendo la propagación de la excepción que ocurrió en el bloque try aunque no se detectó.

Como han dicho otros, aunque es legal regresar de un bloque finalmente de acuerdo con las especificaciones de Java, es algo MALO y no debe hacerse.

John Meagher
fuente
¿Dónde se debe poner la devolución entonces?
analizador
@parsecer diría justo después de llamar a algoThatThrewAnException () dentro del bloque de prueba
Tiago Sippert
@parsecer, ?? Solo hazlo de la forma habitual, después del final.
Pacerier
21

javac le avisará de retorno finalmente si usa -Xlint: finalmente. Originalmente, javac no emitía advertencias: si algo estaba mal con el código, debería fallar al compilar. Desafortunadamente, la compatibilidad con versiones anteriores significa que no se puede prohibir la tontería ingeniosa imprevista.

Se pueden generar excepciones desde finalmente bloques, pero en ese caso el comportamiento exhibido es casi seguro lo que desea.

Tom Hawtin - tackline
fuente
13

Agregar estructuras de control y retornos a los bloques finalmente {} son solo otro ejemplo de abusos "solo porque puedes" que se encuentran dispersos en prácticamente todos los lenguajes de desarrollo. Jason tenía razón al sugerir que podría convertirse fácilmente en una pesadilla de mantenimiento: los argumentos en contra de los primeros retornos de las funciones se aplican más a este caso de "retornos tardíos".

Finalmente, existen bloques para un propósito, para permitirle ordenar completamente por sí mismo, sin importar lo que sucedió en todo el código anterior. Principalmente, esto es cerrar / liberar punteros de archivos, conexiones de bases de datos, etc., aunque pude ver que se estira para decir que se agrega una auditoría a medida.

Cualquier cosa que afecte el retorno de la función debería estar en el bloque try {}. Incluso si tuviera un método mediante el cual verificó un estado externo, realizó una operación que consume mucho tiempo, luego verificó ese estado nuevamente en caso de que se volviera inválido, aún querría la segunda verificación dentro del intento {}, si finalmente se quedó adentro {} y la operación larga falló, entonces verificaría ese estado por segunda vez innecesariamente.

Ian
fuente
6

Una simple prueba de 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)

Salida:

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

Pregunta:

Un punto interesante para mí fue ver cómo Groovy trata con los retornos implícitos. En Groovy es posible "regresar" de un método simplemente dejando un valor al final (sin retorno). ¿Qué crees que sucede si descomentas el runningThreads.remove (..) en la declaración de ? ¿Esto sobrescribirá el valor de retorno regular ("OK") y cubrirá la excepción ?!

Prof. Ondino
fuente
0

Volver desde el interior de un finallybloque hará exceptionsque se pierda.

Una declaración de devolución dentro de un bloque finalmente hará que se descarte cualquier excepción que pueda lanzarse en el bloque try o catch.

De acuerdo con la especificación del lenguaje Java:

Si la ejecución del bloque try se completa abruptamente por cualquier otra razón R, entonces se ejecuta el bloque finalmente y luego hay una opción:

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

Nota: Según JLS 14.17 : una declaración de devolución siempre se completa abruptamente.

Ankur Lathi
fuente