Java try / catch / finally mejores prácticas al adquirir / cerrar recursos

Mientras trabajaba en un proyecto de la escuela, escribí el siguiente código:

FileOutputStream fos; ObjectOutputStream oos; try { fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(shapes); } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } finally { if (oos != null) oos.close(); if (fos != null) fos.close(); } 

El problema es que Netbeans me está diciendo que las líneas resource.close() lanzan una IOException y, por lo tanto, deben capturarse o declararse. También se queja de que oos y fos aún no se oos inicializado (a pesar de los controles nulos).

Esto parece un poco extraño, ya que la cuestión es detener la IOException allí mismo.

Mi solución de rodilla es hacer esto:

 } finally { try { if (oos != null) oos.close(); if (fos != null) fos.close(); } catch (IOException ex) { } } 

Pero en el fondo esto me molesta y me siento sucio.

Vengo de un entorno de C #, donde simplemente aprovecharía un bloque de using , por lo que no estoy seguro de cuál es la forma “correcta” de manejarlo.

¿Cuál es la forma correcta de manejar este problema?

Si intenta atrapar e informar todas las excepciones en el origen, una mejor solución es esta:

 ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(shapes); oos.flush(); } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } finally { if (oos != null) { try { oos.close(); } catch (IOException ex) { // ignore ... any significant errors should already have been // reported via an IOException from the final flush. } } } 

Notas:

  • Las secuencias, los lectores y los escritores de envoltorio de Java estándar se propagan close y al flush a sus flujos envueltos, etc. Por lo tanto, solo tiene que cerrar o eliminar el contenedor más externo.
  • El propósito de vaciar explícitamente al final del bloque try es para que el manejador (real) de IOException pueda ver cualquier error de escritura 1 .
  • Cuando cierra o descarga una secuencia de salida, hay una posibilidad de que “una vez en una luna azul” se produzca una excepción debido a errores de disco o sistema de archivos lleno. ¡No deberías aplastar esta excepción! .

Si a menudo tiene que “cerrar una secuencia posiblemente nula ignorando IOExceptions”, entonces podría escribir un método de ayuda como este:

 public void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException ex) { // ignore } } } 

entonces puedes reemplazar el bloque finally anterior por:

 } finally { closeQuietly(oos); } 

(Otra respuesta señala que un método closeQuietly ya está disponible en una biblioteca de Apache Commons … si no le importa agregar una dependencia a su proyecto para un método de 10 líneas. ACTUALIZACIÓN : tenga en cuenta que estos métodos están en desuso en la versión 2.6 de la API)

Pero tenga cuidado de que solo use closeQuietly en las secuencias donde las excepciones IO realmente no son relevantes.

1 – Eso no es necesario cuando se usa try-with-resources.


Sobre el tema de flush() versus close() que las personas están preguntando acerca de:

  • Las secuencias y los escritores de salida estándar “filtro” y “almacenado en búfer” tienen un contrato de API que establece que close() hace que se vacíe toda la salida almacenada en el búfer. Debería encontrar que todas las demás clases de salida (estándar) que generan búferes de salida se comportarán de la misma manera. Entonces, para una clase estándar es redundante llamar a flush() inmediatamente antes de close() .
  • Para las clases personalizadas y de terceros, necesita investigar (por ejemplo, lea el javadoc, mire el código), pero cualquier método close() que no limpie los datos almacenados temporalmente se puede romper .
  • Finalmente, está la cuestión de qué es lo que flush() realmente hace. Lo que el javadoc dice es esto (para OutputStream …)

    Si el destino previsto de esta secuencia es una abstracción proporcionada por el sistema operativo subyacente, por ejemplo, un archivo, el flujo de la stream garantiza únicamente que los bytes escritos previamente en la secuencia se pasan al sistema operativo para su escritura; no garantiza que estén escritos en un dispositivo físico, como una unidad de disco.

    Entonces … si esperas / imagino que llamar a flush() garantiza que tus datos persistirán, ¡estás equivocado! (Si necesita hacer ese tipo de cosas, mire el método FileChannel.force …)


Por otro lado, si puede usar Java 7 o posterior, la “nueva” prueba-con-recursos como se describe en la respuesta de @Mike Clark es la mejor solución.

Si no está utilizando Java 7 o posterior para su nuevo código, probablemente se encuentre en un agujero y profundice.

La mejor práctica actual para try / catch / finally que involucra objetos que se pueden cerrar (por ejemplo, archivos) es usar el enunciado try-with-resource de Java 7, por ejemplo:

 try (FileReader reader = new FileReader("ex.txt")) { System.out.println((char)reader.read()); } catch (IOException ioe) { ioe.printStackTrace(); } 

En este caso, el FileReader se cierra automáticamente al final de la instrucción try, sin la necesidad de cerrarlo en un bloque finally explícito. Hay algunos ejemplos aquí:

http://ppkwok.blogspot.com/2012/11/java-cafe-2-try-with-resources.html

La descripción oficial de Java está en:

http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html

Java 7 agregará bloques de Gestión automática de recursos . Son muy similares al uso de C #.

Josh Bloch escribió la propuesta técnica , que recomiendo leer. No solo porque le dará una ventaja sobre una próxima característica del lenguaje Java 7, sino porque la especificación motiva la necesidad de tal construcción, y al hacerlo, ilustra cómo escribir el código correcto incluso en ausencia de ARM.

Aquí hay un ejemplo del código de Asker, traducido a la forma ARM:

 try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(shapes); } catch (FileNotFoundException ex) { // handle the file not being found } catch (IOException ex) { // handle some I/O problem } 

Normalmente tengo una clase pequeña IOUtil con un método como:

 public static void close(Closeable c) { if (c != null) { try { c.close(); } catch (IOException e) { // ignore or log } } } 

¿Qué tal esto chicos? Sin verificación nula, sin sorpresa. Todo se limpia a la salida.

 try { final FileOutputStream fos = new FileOutputStream(file); try { final ObjectOutputStream oos = new ObjectOutputStream(fos); try { oos.writeObject(shapes); oos.flush(); } catch(IOException ioe) { // notify user of important exception } finally { oos.close(); } } finally { fos.close(); } } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } 

Lamentablemente, no hay soporte de nivel de idioma. Pero hay muchas bibliotecas por ahí que lo hacen simple. Verifique la biblioteca commons-io. O moderno google-guava @ http://guava-libraries.googlecode.com/svn/trunk/javadoc/index.html

Lo estás haciendo bien. Me molesta la mierda también. Debe inicializar esas transmisiones para anular explícitamente, es una convención común. Todo lo que puedes hacer es unirte al club y querer using .

No es una respuesta directa a su punto, pero es un hecho desafortunado que, como finally y la catch están asociadas con la try gente piensa que pertenecen juntas. El mejor diseño para try blocks es tener un catch o un finally pero no ambos.

En este caso, sus comentarios sugieren que algo anda mal. Por qué, en un método relacionado con el archivo IO, nos quejamos de cualquier cosa al usuario. Podríamos estar corriendo en un servidor en algún lugar con nary un usuario a la vista.

Entonces, el código que presenta arriba debería finally fallar con gracia cuando las cosas van mal. Sin embargo, carece de la capacidad para tratar inteligentemente los errores, por lo que su catch pertenece a un lugar más arriba en la cadena de llamadas.