java - excepciones - ¿Cuál es el motivo de esta cláusula finally que contiene llamadas close()
try catch java exception e (9)
Estoy estudiando el curso en línea de Java, Introducción a la programación con Java .
En el capítulo sobre E / S, se introduce el siguiente código con la siguiente declaración:
Por cierto, al final de este programa, encontrará nuestro primer ejemplo útil de una cláusula finally en una declaración try. Cuando la computadora ejecuta una instrucción try, se garantiza que los comandos en su cláusula finally serán ejecutados, no importa qué.
El programa se encuentra al final de la sección 11.2.1 y es un programa simple que solo lee algunos números de un archivo y los escribe en orden inverso.
El código relevante en el método principal es (los datos son un lector y el resultado es un escritor):
try {
// Read numbers from the input file, adding them to the ArrayList.
while ( data.eof() == false ) { // Read until end-of-file.
double inputNumber = data.getlnDouble();
numbers.add( inputNumber );
}
// Output the numbers in reverse order.
for (int i = numbers.size()-1; i >= 0; i--)
result.println(numbers.get(i));
System.out.println("Done!");
} catch (IOException e) {
// Some problem reading the data from the input file.
System.out.println("Input Error: " + e.getMessage());
} finally {
// Finish by closing the files, whatever else may have happened.
data.close();
result.close();
}
Así que me preguntaba por qué la cláusula finally fue útil en este caso cuando no hay otros puntos de salida de las cláusulas try o catch. ¿No podrían los métodos de cierre simplemente estar en el cuerpo de main en su lugar?
Pensé que tal vez era porque teóricamente podría haber alguna otra RuntimeException que pudiera bloquear el programa y luego dejar el Reader & Writers sin cerrar, ¿pero entonces no sería el hecho de que el programa se hubiera cerrado de todos modos?
Cierto. Si ocurre RuntimeException
, finalmente se ejecutará, cerrando los recursos y esto no es teóricamente. Este es un escenario práctico.
Además, incluso si IOException
(o muchos otros que haya capturado). finally
cláusula le impide escribir el mismo código para cerrar muchas veces.
Desde el documento de Java:
El bloque finally siempre se ejecuta cuando sale el bloque try. Esto asegura que el bloque finally se ejecuta incluso si ocurre una excepción inesperada. Pero, finalmente, es útil para algo más que el manejo de excepciones: permite que el programador evite que el código de limpieza sea anulado accidentalmente por un retorno, continuación o interrupción. Poner el código de limpieza en un bloque finally es siempre una buena práctica, incluso cuando no se prevén excepciones.
Y sobre sus preocupaciones sobre:
el hecho de que el programa se hubiera estrellado cerca de ellos de todos modos.
Los recursos se asignan en el nivel del OS
, no en su programa, por lo que si su programa no tiene la posibilidad de limpiar, los recursos se asignarán sin utilizar realmente.
En el caso en que se arroje o no una excepción, la cláusula finally
se asegurará de que tanto los data
como los flujos de result
estén cerrados. De lo contrario, es posible que no estén cerrados.
Esto es por una razón muy simple: es la forma más segura, pre Java 7 y try-with-resources, para garantizar que sus recursos estén cerrados incluso si se detecta una excepción.
Considere lo que sucedió si lo hizo en su lugar:
try {
// some code, then
resource.close();
} catch (SomeException e) {
// etc
}
Si se lanza su SomeException
antes de que se cierre el recurso, puede perder recursos. Poner el resource.close()
finally
, por otro lado, garantiza que se cierra sin importar lo que suceda.
Con Java 7 usarías esto:
try (
final InputStream in = Files.newInputStream(Paths.get("somefile"));
// Others
) {
// work with "in" and others
} catch (Whatever e) {
}
Sus recursos se cerrarán antes de la catch
.
Como nota al margen, usando Java 6, la forma más segura de tener tus recursos cerrados es usar Guava''s Closer
.
Por último, asegura que siempre se llama una pieza de código. Por eso, es el mejor lugar para cerrar las conexiones. Aconsejaría poner sus declaraciones cercanas en una captura de prueba también, ya que algo puede salir mal en el bloque finally:
finally {
if (data != null) try { data.close() } catch(exception ex) { ex.printstacktrace() }
}
Si el programa finalizó después de eso, entonces sí, eso también cerraría los recursos de E / S.
Pero muchos programas no terminan. Hay algunos que deben funcionar 24/7 durante años. Por lo tanto, limpiar sus recursos correctamente es imprescindible.
Desafortunadamente, Java <7 solo ofrece un mecanismo de limpieza automático para la memoria (recolección de basura). Con Java 7, obtienes la nueva " declaración de intentar con recursos " que intenta tapar el agujero.
A menos que pueda usar esta versión, debe hacer la limpieza usted mismo.
Dicho esto, el código anterior aún tiene fallas: close()
puede lanzar una excepción, por lo que algunos de los recursos de E / S podrían mantenerse. En su lugar, debe usar herramientas como IOUtils.closeQuietly () :
Reader reader = null;
try {
reader = ...open...
...use reader...
} finally {
IOUtils.closeQuietly(reader);
}
Un método "cerrado" puede hacer más que simplemente notificar a un sistema operativo que un recurso ya no es necesario. Entre otras cosas, los objetos que encapsulan varias formas de flujos de datos pueden almacenar una cierta cantidad de datos y pasarlos al sistema operativo, ya sea cuando están cerrados o cuando se ha acumulado una cierta cantidad de datos. Tener un objeto que encapsula una secuencia de datos de registro para transferir datos al sistema operativo en bloques grandes puede ser mucho más eficiente que hacer que pase eventos de registro al sistema operativo individualmente, pero si el programa muere sin "cerrar" el archivo de registro, información que probablemente sea muy relevante para cualquier persona que diagnostique que el problema se perderá.
Ese es realmente un concepto correcto que has formado. Cuando se CheckedException
una IOException
CheckedException
como IOException
, todos los recursos deben estar cerrados. Esto es porque:
Cuando estaba ejecutando el programa, se accedía a los recursos. Suponiendo que hayas cambiado algunas cosas y no las hayas guardado, no obtendrás el archivo actualizado. (Esto sucede cuando usa Buffers - No escriben los datos instantáneamente, escriben en fragmentos ).
Como el archivo se abrió en la JVM, hay una probabilidad de que permanezca abierto y deberá cerrar la aplicación que está usando. Por lo tanto, debe
close()
los recursos para que los búferes se vacíen, es decir, se guarden los cambios.
Por ejemplo:
try {
BufferedReader br = new BufferredReader (new FileReader("Example.txt"));
ArrayList<String> lines = new ArrayList<>();
String line;
while ( (line = br.readLine()) != null ) {
lines.add(line);
}
catch (IOException ie) {
//Error handling.
} finally {
br.close();
}
EDITAR: con la llegada de JDK1.7, ahora puede usar try with resources
, que se muestra a continuación:
try (BufferedReader br = new BufferredReader (new FileReader("Example.txt"));) {
ArrayList<String> lines = new ArrayList<>();
String line;
while ( (line = br.readLine()) != null ) {
lines.add(line);
}
catch (IOException ie) {
//Error handling.
}
Ahora, el bloque finally
no es necesario porque BufferedReader
implementa AutoCloseable
. Como su nombre indica, cierra automáticamente el búfer cuando queda el bloque try (..)
.
Tu idea es correcta: el bloque finally cerrará los recursos incluso si ocurrió una excepción inesperada.
También tiene razón en que esto es irrelevante si tal excepción bloquea la aplicación completa, pero al mirar este código no puede decidir si este es el caso. Puede haber otros manejadores de excepciones, atrapando esa excepción, por lo que es una buena y correcta práctica poner la lógica de cierre en un bloque final.
Tenga en cuenta que todavía puede haber un error de ocultación: si data.close()
arroja una excepción, result.close()
nunca se llamará.
Dependiendo de su entorno, hay varios sabores sobre cómo solucionar el error.
en java 7 ff puedes usar try-with-resources
si está utilizando Spring, puede haber una plantilla adecuada similar a JdbcTemplate
si nada de eso se aplica, sí, tendrás que hacer una prueba / finalmente dentro del final. Deja feo. Absolutamente deberías al menos extraer esto en un método como se sugiere en los comentarios.
conceptualmente más limpio pero bastante prolijo en java pre 8 es implementar el patrón de préstamo . Si no trabajas con desarrolladores scala / clojure / haskell, puede ser más confuso que cualquier otra cosa.