lanza Exception en bloques finalmente

100

¿Existe una forma elegante de manejar las excepciones que se lanzan en finallybloque?

Por ejemplo:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

¿Cómo evitas el try/ catchen el finallybloque?

Pablo
fuente

Respuestas:

72

Normalmente lo hago así:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

En otra parte:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}
Darron
fuente
4
Sí, utilizo un modismo muy similar. Pero no creo una función para eso.
OscarRyz
9
Una función es útil si necesita usar el modismo en algunos lugares de la misma clase.
Darron
La comprobación de nulo es redundante. Si el recurso era nulo, entonces el método de llamada está roto debería arreglarse. Además, si el recurso es nulo, probablemente debería registrarse. De lo contrario, se ignorará silenciosamente una posible excepción.
Dave Jarvis
14
La comprobación de nulos no siempre es redundante. Piense en "resource = new FileInputStream (" file.txt ")" como la primera línea del intento. Además, esta pregunta no se refería a la programación orientada a aspectos que muchas personas no utilizan. Sin embargo, el concepto de que la excepción no debe ignorarse simplemente se manejó de manera más compacta mostrando una declaración de registro.
Darron
1
Resource=> Closeable?
Dmitry Ginzburg
25

Normalmente uso uno de los closeQuietlymétodos en org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}
CJS
fuente
3
Puede hacer que este método sea más general con Closeable public static void closeQuietly (Closeable closeable) {
Peter Lawrey
6
Sí, Closeable es agradable. Es una pena que muchas cosas (como los recursos JDBC) no lo implementen.
Darron
22

Si está usando Java 7 y lo resourceimplementa AutoClosable, puede hacer esto (usando InputStream como ejemplo):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
Kevin Wong
fuente
8

Podría decirse que es un poco exagerado, pero tal vez útil si está permitiendo que surjan excepciones y no puede registrar nada desde su método (por ejemplo, porque es una biblioteca y prefiere dejar que el código de llamada maneje las excepciones y el registro):

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

ACTUALIZACIÓN: Revisé esto un poco más y encontré una excelente publicación de blog de alguien que claramente ha pensado en esto más que yo: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Va un paso más allá y combina las dos excepciones en una, lo que podría ser útil en algunos casos.

MEGABYTE.
fuente
1
+1 para el enlace del blog. Además, al menos registraría la ignoreexcepción
Denis Kniazhev
6

A partir de Java 7, ya no es necesario cerrar explícitamente los recursos en un bloque finalmente , sino que puede usar la sintaxis try -with-resources. La declaración try-with-resources es una declaración try que declara uno o más recursos. Un recurso es un objeto que debe cerrarse después de que el programa haya terminado con él. La declaración try-with-resources asegura que cada recurso se cierre al final de la declaración. Cualquier objeto que implemente java.lang.AutoCloseable, que incluye todos los objetos que implementan java.io.Closeable, se puede usar como recurso.

Suponga el siguiente código:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

Si ocurre alguna excepción , se llamará al método close en cada uno de estos tres recursos en orden opuesto al que fueron creados. Significa que el método de cierre se llamará primero para ResultSetm, luego la Declaración y al final para el objeto Connection.

También es importante saber que se suprimen las excepciones que ocurren cuando se llama automáticamente a los métodos de cierre. Estas excepciones suprimidas se pueden recuperar mediante el método getsuppressed () definido en la clase Throwable .

Fuente: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

Soroosh
fuente
Parece incompleto que esta respuesta no mencione la diferencia de comportamiento entre este enfoque y cómo funciona el código de ejemplo publicado en el OP.
Nathan Hughes
2
el uso de try-with-resources arroja una excepción al cerrar si la parte en el bloque try se completa normalmente pero el método de cierre no lo hace, a diferencia de lo que hace el código OP. recomendarlo como reemplazo sin reconocer el cambio de comportamiento parece potencialmente engañoso.
Nathan Hughes
No lanza una excepción, el método de cierre se llama automáticamente y se suprime.
Soroosh
2
prueba el caso que describí. try block se completa normalmente, close lanza algo. y vuelva a leer la página en la que publicó el enlace, la supresión solo se aplica cuando el bloque try arroja algo.
Nathan Hughes
3

Ignorar las excepciones que ocurren en un bloque 'finalmente' es generalmente una mala idea a menos que uno sepa cuáles serán esas excepciones y qué condiciones representarán. En el try/finallypatrón de uso normal , el trybloque coloca las cosas en un estado que el código externo no espera, y el finallybloque restaura el estado de esas cosas a lo que espera el código externo. El código externo que detecta una excepción generalmente esperará que, a pesar de la excepción, todo se haya restaurado a unnormalestado. Por ejemplo, suponga que algún código inicia una transacción y luego intenta agregar dos registros; el bloque "finalmente" realiza una operación de "reversión si no se ha confirmado". Una persona que llama puede estar preparada para que ocurra una excepción durante la ejecución de la segunda operación "agregar", y puede esperar que si detecta dicha excepción, la base de datos estará en el estado en el que estaba antes de intentar cualquier operación. Sin embargo, si se produce una segunda excepción durante la reversión, podrían suceder cosas malas si la persona que llama hace suposiciones sobre el estado de la base de datos. El error de reversión representa una crisis importante , una que no debe ser detectada por el código esperando una simple excepción de "No se pudo agregar el registro".

Mi inclinación personal sería tener un método de captura de las excepciones que ocurren y envolverlas en una "CleanupFailedException", reconociendo que tal falla representa un problema importante y que tal excepción no debe tomarse a la ligera.

Super gato
fuente
2

Una solución, si las dos excepciones son dos clases diferentes

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

Pero a veces no se puede evitar este segundo intento. p. ej. para cerrar una corriente

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }
Pierre
fuente
En su caso, si utilizó una instrucción "using", debería limpiar el recurso.
Chuck Conway
Mi mal, supongo que es C #.
Chuck Conway
1

¿Por qué quiere evitar el bloqueo adicional? Dado que el bloque finalmente contiene operaciones "normales" que pueden generar una excepción Y desea que el bloque finalmente se ejecute por completo, TIENE que detectar excepciones.

Si no espera que el bloque finalmente arroje una excepción y no sabe cómo manejar la excepción de todos modos (simplemente volcaría el seguimiento de la pila) deje que la excepción burbujee en la pila de llamadas (elimine el try-catch de la última bloquear).

Si desea reducir la escritura, puede implementar un bloque try-catch externo "global", que capturará todas las excepciones lanzadas en los bloques finalmente:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}
Eduard Wirch
fuente
2
-1 Para este también. ¿Qué sucede si está intentando cerrar varios recursos en un solo bloque finalmente? Si el cierre del primer recurso falla, los demás permanecerán abiertos una vez que se lance la excepción.
Programador fuera de la ley
Es por eso que le dije a Paul que TIENES que detectar excepciones si quieres asegurarte de que el bloque finalmente se complete. ¡Lea TODA la respuesta!
Eduard Wirch
1

Después de mucha consideración, encuentro mejor el siguiente código:

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

Ese código garantiza lo siguiente:

  1. El recurso se libera cuando finaliza el código.
  2. Las excepciones lanzadas al cerrar el recurso no se consumen sin procesarlas.
  3. El código no intenta cerrar el recurso dos veces, no se creará ninguna excepción innecesaria.
Grogi
fuente
También puede evitar llamar a resource.close (); resource = null en el bloque try, para eso están los bloques finalmente. También tenga en cuenta que no maneja ninguna excepción lanzada mientras "hace algo elegante", que en realidad, creo que prefiero mejor, para manejar excepciones de infraestructura en un nivel de aplicación más alto en la pila.
Paul
El resource.close () también puede lanzar una excepción, es decir, cuando falla el vaciado del búfer. Esta excepción nunca debe consumirse. Sin embargo, si se cierra la secuencia como resultado de una excepción generada anteriormente, el recurso debe cerrarse silenciosamente ignorando la excepción y preservando la causa raíz.
Grogi
0

Si puede, primero debe probar para evitar la condición de error.

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

Además, probablemente solo debería detectar excepciones de las que puede recuperarse, si no puede recuperar, deje que se propague al nivel superior de su programa. Si no puede probar una condición de error, tendrá que rodear su código con un bloque try catch como ya lo ha hecho (aunque recomendaría detectar errores específicos esperados).

Ken Henderson
fuente
En general, probar las condiciones de error es una buena práctica, simplemente porque las excepciones son caras.
Dirk Vollmar
La "programación defensiva" es un paradigma anticuado. El código inflado que resulta de probar todas las condiciones de error eventualmente causa más problemas de los que resuelve. TDD y el manejo de excepciones es ese enfoque moderno en mi humilde opinión
Joe Soul-bringer
@Joe: no estoy en desacuerdo con usted sobre la prueba de todas las condiciones de error, pero a veces tiene sentido, especialmente a la luz de la diferencia (normalmente) en el costo de una verificación simple para evitar la excepción frente a la excepción en sí.
Ken Henderson
1
-1 Aquí, resource.Close () puede lanzar una excepción. Si necesita cerrar recursos adicionales, la excepción haría que la función regresara y permanecerían abiertos. Ese es el propósito del segundo intento / captura en el OP.
Programador fuera de la ley
@Outlaw: estás perdiendo mi punto si Close arroja una excepción y el recurso está abierto, luego, al capturar y suprimir la excepción, ¿cómo corrijo el problema? De ahí por qué dejo que se propague (es bastante raro que pueda recuperarlo con él todavía abierto).
Ken Henderson
0

Podrías refactorizar esto en otro método ...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}
Sam Saffron
fuente
0

Yo suelo hacer esto:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

Justificación: si terminé con el recurso y el único problema que tengo es cerrarlo, no hay mucho que pueda hacer al respecto. Tampoco tiene sentido matar todo el hilo si de todos modos he terminado con el recurso.

Este es uno de los casos en los que, al menos para mí, es seguro ignorar esa excepción marcada.

Hasta el día de hoy no he tenido ningún problema para usar este idioma.

OscarRyz
fuente
Lo registraría, en caso de que encuentre algunas fugas en el futuro. De esa manera sabría de dónde podrían (no) venir
Egwor
@Egwor. Estoy de acuerdo contigo. Esto fue solo un títere rápido. También lo registro y probablemente use una captura es algo que se podría hacer con la excepción :)
OscarRyz
0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Trabajo hecho. Sin pruebas nulas. Captura única, incluye excepciones de adquisición y liberación. Por supuesto, puede usar el idioma Execute Around y solo tiene que escribirlo una vez para cada tipo de recurso.

Tom Hawtin - tackline
fuente
5
¿Qué pasa si use (resource) arroja la excepción A y luego resource.release () arroja la excepción B? Excepción A se pierde ...
Darron
0

Cambiar Resourcede la mejor respuesta aCloseable

Implementos de corrientes CloseablePor lo tanto, puede reutilizar el método para todas las corrientes

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}
Ryan
fuente
0

Encontré una situación similar en la que no podía usar try con recursos, pero también quería manejar la excepción proveniente del cierre, no solo registrarla e ignorarla como lo hace el mecanismo closeQuietly. en mi caso, no estoy tratando con un flujo de salida, por lo que la falla al cerrar es de más interés que un flujo simple.

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
David Bradley
fuente