La sintaxis de prueba con recursos de Java 7 (también conocida como bloque ARM ( Gestión automática de recursos )) es agradable, corta y directa cuando se usa solo un AutoCloseable
recurso. Sin embargo, no estoy seguro de lo que es el idioma correcto cuando necesito para declarar múltiples recursos que son dependientes entre sí, por ejemplo, un FileWriter
y una BufferedWriter
que lo envuelve. Por supuesto, esta pregunta se refiere a cualquier caso cuando AutoCloseable
se envuelven algunos recursos, no solo estas dos clases específicas.
Se me ocurrieron las tres alternativas siguientes:
1)
El lenguaje ingenuo que he visto es declarar solo el contenedor de nivel superior en la variable administrada por ARM:
static void printToFile1(String text, File file) {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
bw.write(text);
} catch (IOException ex) {
// handle ex
}
}
Esto es bueno y corto, pero está roto. Debido a que el subyacente FileWriter
no se declara en una variable, nunca se cerrará directamente en el finally
bloque generado . Se cerrará solo a través del close
método de envoltura BufferedWriter
. El problema es que si se lanza una excepción desde el bw
constructor, close
no se llamará y, por lo tanto, el subyacente FileWriter
no se cerrará .
2)
static void printToFile2(String text, File file) {
try (FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw)) {
bw.write(text);
} catch (IOException ex) {
// handle ex
}
}
Aquí, tanto el recurso subyacente como el envoltorio se declaran en las variables administradas por ARM, por lo que ambos se cerrarán, pero el subyacente fw.close()
se llamará dos veces : no solo directamente, sino también a través del ajuste bw.close()
.
Esto no debería ser un problema para estas dos clases específicas que ambas implementan Closeable
(que es un subtipo de AutoCloseable
), cuyo contrato establece que close
se permiten múltiples llamadas a :
Cierra esta secuencia y libera todos los recursos del sistema asociados a ella. Si la secuencia ya está cerrada, invocar este método no tiene ningún efecto.
Sin embargo, en un caso general, puedo tener recursos que se implementan solo AutoCloseable
(y no Closeable
), lo que no garantiza que close
se pueda llamar varias veces:
Tenga en cuenta que, a diferencia del método de cierre de java.io.Closeable, no es necesario que este método de cierre sea idempotente. En otras palabras, llamar a este método de cierre más de una vez puede tener algún efecto secundario visible, a diferencia de Closeable.close, que se requiere que no tenga efecto si se llama más de una vez. Sin embargo, se recomienda encarecidamente a los implementadores de esta interfaz que hagan idempotentes sus métodos cercanos.
3)
static void printToFile3(String text, File file) {
try (FileWriter fw = new FileWriter(file)) {
BufferedWriter bw = new BufferedWriter(fw);
bw.write(text);
} catch (IOException ex) {
// handle ex
}
}
Esta versión debería ser teóricamente correcta, porque solo fw
representa un recurso real que necesita ser limpiado. El bw
mismo no contiene ningún recurso, solo delega en el fw
, por lo que debería ser suficiente para cerrar solo el subyacente fw
.
Por otro lado, la sintaxis es un poco irregular y, además, Eclipse emite una advertencia, que creo que es una falsa alarma, pero sigue siendo una advertencia con la que uno tiene que lidiar:
Fuga de recursos: 'bw' nunca se cierra
Entonces, ¿qué enfoque elegir? ¿O me he perdido algún otro idioma que es el correcto ?
fuente
public BufferedWriter(Writer out, int sz)
puede lanzar unIllegalArgumentException
. Además, puedo extender BufferedWriter con una clase que arrojaría algo de su constructor o crearía cualquier contenedor personalizado que necesite.BufferedWriter
constructor puede lanzar fácilmente una excepción.OutOfMemoryError
es probablemente el más común, ya que asigna una buena cantidad de memoria para el búfer (aunque puede indicar que desea reiniciar todo el proceso). / Necesitasflush
tuBufferedWriter
si no cierras y quieres conservar el contenido (generalmente solo el caso sin excepción).FileWriter
recoge lo que sea que sea la codificación de archivo "predeterminada"; es mejor ser explícito.Respuestas:
Aquí está mi opinión sobre las alternativas:
1)
Para mí, lo mejor que llegó a Java desde C ++ tradicional hace 15 años fue que podía confiar en su programa. Incluso si las cosas están mal y van mal, lo que a menudo hacen, quiero que el resto del código tenga el mejor comportamiento y el olor a rosas. De hecho, el
BufferedWriter
podría arrojar una excepción aquí. Quedarse sin memoria no sería inusual, por ejemplo. Para otros decoradores, ¿sabe cuál de lasjava.io
clases de envoltura arroja una excepción marcada de sus constructores? Yo no. La comprensión del código no es muy buena si confía en ese tipo de conocimiento oscuro.También está la "destrucción". Si hay una condición de error, entonces probablemente no desee tirar basura a un archivo que necesita eliminarse (el código para eso no se muestra). Aunque, por supuesto, eliminar el archivo también es otra operación interesante para manejar errores.
En general, desea que los
finally
bloques sean lo más cortos y confiables posible. Agregar rubores no ayuda a este objetivo. Para muchas versiones, algunas de las clases de almacenamiento en búfer en el JDK tenían un error en el que no se llamaba una excepciónflush
internaclose
causadaclose
en el objeto decorado. Si bien eso se ha solucionado durante algún tiempo, espere de otras implementaciones.2)
Todavía nos estamos volcando en el bloque implícito finalmente (ahora con repetición
close
, esto empeora a medida que agrega más decoradores), pero la construcción es segura y tenemos que bloquear implícitamente finalmente para que incluso un errorflush
no impida la liberación de recursos.3)
Hay un error aquí. Debiera ser:
Algunos decoradores mal implementados son de hecho recursos y deberán cerrarse de manera confiable. Además, es posible que algunas secuencias deban cerrarse de una manera particular (tal vez están haciendo compresión y necesitan escribir bits para terminar, y no pueden simplemente vaciar todo.
Veredicto
Aunque 3 es una solución técnicamente superior, las razones de desarrollo de software hacen de 2 la mejor opción. Sin embargo, probar con recursos sigue siendo una solución inadecuada y debe seguir con el lenguaje Execute Around , que debería tener una sintaxis más clara con los cierres en Java SE 8.
fuente
El primer estilo es el sugerido por Oracle .
BufferedWriter
no arroja excepciones comprobadas, por lo que si se lanza alguna excepción, no se espera que el programa se recupere, lo que hace que la recuperación de recursos sea principalmente discutible.Principalmente porque podría suceder en un subproceso, con la interrupción del subproceso pero el programa aún continúa; por ejemplo, hubo una interrupción temporal de la memoria que no fue lo suficientemente larga como para perjudicar gravemente al resto del programa. Sin embargo, es un caso bastante arrinconado, y si sucede con la frecuencia suficiente como para hacer que la fuga de recursos sea un problema, la prueba con recursos es el menor de sus problemas.
fuente
Opcion 4
Cambie sus recursos para que se puedan cerrar, no se puedan cerrar automáticamente si puede. El hecho de que los constructores se puedan encadenar implica que no es extraño cerrar el recurso dos veces. (Esto era cierto antes de ARM también.) Más sobre esto a continuación.
Opción 5
¡No use ARM y código con mucho cuidado para garantizar que close () no se llame dos veces!
Opcion 6
No uses ARM y ten tus llamadas finalmente close () en un intento / captura ellos mismos.
Por qué no creo que este problema sea exclusivo de ARM
En todos estos ejemplos, las llamadas finalmente close () deben estar en un bloque catch. Dejado por legibilidad.
No es bueno porque fw se puede cerrar dos veces. (que está bien para FileWriter pero no en su ejemplo hipotético):
No es bueno porque fw no se cierra si es una excepción al construir un BufferedWriter. (de nuevo, no puede suceder, pero en su ejemplo hipotético):
fuente
Solo quería basarme en la sugerencia de Jeanne Boyarsky de no usar ARM pero asegurarme de que FileWriter siempre esté cerrado exactamente una vez. No pienses que hay problemas aquí ...
Supongo que dado que ARM es solo azúcar sintáctico, no siempre podemos usarlo para reemplazar finalmente los bloques. Al igual que no siempre podemos usar un ciclo for-each para hacer algo que sea posible con iteradores.
fuente
try
como losfinally
bloques arrojan excepciones, esta construcción perderá la primera (y potencialmente más útil).Para coincidir con los comentarios anteriores: lo más simple es (2) usar
Closeable
recursos y declararlos en orden en la cláusula try-with-resources. Si solo tieneAutoCloseable
, puede envolverlos en otra clase (anidada) que solo verifica queclose
solo se llama una vez (Patrón de fachada), por ejemplo, al tenerprivate bool isClosed;
. En la práctica, incluso Oracle solo (1) encadena los constructores y no maneja correctamente las excepciones en la mitad de la cadena.Alternativamente, puede crear manualmente un recurso encadenado, utilizando un método de fábrica estático; esto encapsula la cadena y maneja la limpieza si falla parcialmente:
Luego puede usarlo como un recurso único en una cláusula de prueba con recursos:
La complejidad proviene de manejar múltiples excepciones; de lo contrario, son solo "recursos cercanos que has adquirido hasta ahora". Una práctica común parece ser inicializar primero la variable que contiene el objeto que contiene el recurso
null
(aquífileWriter
), y luego incluir una comprobación nula en la limpieza, pero eso parece innecesario: si el constructor falla, no hay nada que limpiar, así que podemos dejar que se propague esa excepción, lo que simplifica un poco el código.Probablemente podrías hacer esto genéricamente:
Del mismo modo, puede encadenar tres recursos, etc.
Como matemático aparte, incluso podría encadenar tres veces encadenando dos recursos a la vez, y sería asociativo, lo que significa que obtendría el mismo objeto en caso de éxito (porque los constructores son asociativos), y las mismas excepciones si hubiera un fallo en cualquiera de los constructores. Suponiendo que agregó una S a la cadena anterior (por lo que comienza con una V y termina con una S , aplicando U , T y S a su vez), obtiene lo mismo si primero encadena S y T , luego U , correspondiente a (ST) U , o si primero encadenó T y U , entoncesS , correspondiente a S (TU) . Sin embargo, sería más claro simplemente escribir una cadena triple explícita en una sola función de fábrica.
fuente
try (BufferedWriter writer = <BufferedWriter, FileWriter>createChainedResource(file)) { /* work with writer */ }
?Como sus recursos están anidados, sus cláusulas de prueba también deberían ser:
fuente
close
dos veces.Yo diría que no use ARM y continúe con Closeable. Use un método como,
También debe considerar llamar cerca
BufferedWriter
ya que no solo delega el cierreFileWriter
, sino que también realiza algunas tareas de limpiezaflushBuffer
.fuente
Mi solución es hacer una refactorización de "método de extracción", de la siguiente manera:
printToFile
se puede escribiro
Para los diseñadores de la clase lib, les sugeriré que amplíen la
AutoClosable
interfaz con un método adicional para suprimir el cierre. En este caso, podemos controlar manualmente el comportamiento cercano.Para los diseñadores de idiomas, la lección es que agregar una nueva característica podría significar agregar muchas otras. En este caso de Java, obviamente la función ARM funcionará mejor con un mecanismo de transferencia de propiedad de recursos.
ACTUALIZAR
Originalmente, el código anterior requiere
@SuppressWarning
ya que elBufferedWriter
interior de la función lo requiereclose()
.Como sugiere un comentario, si se
flush()
debe llamar antes de cerrar el escritor, debemos hacerlo antes de cualquierreturn
declaración (implícita o explícita) dentro del bloque try. Actualmente no hay forma de garantizar que la persona que llama haga esto, creo, por lo que debe documentarsewriteFileWriter
.ACTUALIZAR DE NUEVO
La actualización anterior hace
@SuppressWarning
innecesaria, ya que requiere que la función devuelva el recurso a la persona que llama, por lo que no es necesario cerrarla. Desafortunadamente, esto nos lleva de vuelta al comienzo de la situación: la advertencia ahora se mueve nuevamente al lado de la persona que llama.Así que para resolver adecuadamente esto, necesitamos un personalizarse
AutoClosable
que cada vez que se cierra, el subrayadoBufferedWriter
seráflush()
ed. En realidad, esto nos muestra otra forma de evitar la advertencia, yaBufferWriter
que nunca se cierra de ninguna manera.fuente
bw
realmente escribirá los datos? Se está amortiguada después de todo, por lo que sólo tiene que escribir en el disco a veces (cuando el búfer está lleno y / o sobreflush()
yclose()
métodos). Supongo que elflush()
método debería llamarse. Pero, de todos modos, no hay necesidad de usar el escritor almacenado en búfer, cuando lo escribimos inmediatamente en un lote. Y si no arregla el código, podría terminar con los datos escritos en el archivo en un orden incorrecto, o incluso no escritos en el archivo.flush()
es necesario llamar, sucederá cada vez que la persona que llama decida cerrar elFileWriter
. Entonces, esto debería sucederprintToFile
justo antes de cualquierreturn
s en el bloque try. Esto no formará partewriteFileWriter
y, por lo tanto, la advertencia no se trata de nada dentro de esa función, sino del llamador de esa función. Si tenemos una anotación@LiftWarningToCaller("wanrningXXX")
ayudará a este caso y similares.