finanzas - RAII en Java... ¿la eliminación de recursos siempre es tan fea?
rai finanzas (5)
Desafortunadamente, este tipo de código tiende a hincharse un poco en Java.
Por cierto, si una de las llamadas a oSBuffer.read o oDBuffer.write arroja una excepción, entonces probablemente quiera permitir que esa excepción penetre en la jerarquía de llamadas.
Tener una llamada desprotegida para cerrar () dentro de una cláusula finally hará que la excepción original sea reemplazada por una producida por la llamada close (). En otras palabras, un método close () fallido puede ocultar la excepción original producida por read () o write (). Por lo tanto, creo que desea ignorar las excepciones lanzadas por close () si y solo si los otros métodos no arrojaron.
Normalmente resuelvo esto al incluir una llamada directa explícita, dentro del intento interno:
try { while (...) { read... write... } oSBuffer.close(); // exception NOT ignored here oDBuffer.close(); // exception NOT ignored here } finally { silentClose(oSBuffer); // exception ignored here silentClose(oDBuffer); // exception ignored here }
static void silentClose(Closeable c) { try { c.close(); } catch (IOException ie) { // Ignored; caller must have this intention } }
Finalmente, para el rendimiento, el código probablemente debería funcionar con búferes (múltiples bytes por lectura / escritura). No puede respaldar eso por números, pero menos llamadas deberían ser más eficientes que agregar secuencias almacenadas en búfer en la parte superior.
Acabo de jugar con la API del sistema de archivos de Java y obtuve la siguiente función, que se utiliza para copiar archivos binarios. La fuente original provino de la Web, pero agregué cláusulas try / catch / finally para asegurarme de que, en caso de que ocurriera algo mal, se cerraran los Buffer Streams (y, por lo tanto, mis recursos de SO liberados) antes de abandonar la función.
Recorté la función para mostrar el patrón:
public static void copyFile(FileOutputStream oDStream, FileInputStream oSStream) throw etc...
{
BufferedInputStream oSBuffer = new BufferedInputStream(oSStream, 4096);
BufferedOutputStream oDBuffer = new BufferedOutputStream(oDStream, 4096);
try
{
try
{
int c;
while((c = oSBuffer.read()) != -1) // could throw a IOException
{
oDBuffer.write(c); // could throw a IOException
}
}
finally
{
oDBuffer.close(); // could throw a IOException
}
}
finally
{
oSBuffer.close(); // could throw a IOException
}
}
Por lo que yo entiendo, no puedo poner los dos close()
en la cláusula finally porque el primer close()
podría arrojarse, y luego, el segundo no se ejecutaría.
Sé que C # tiene el patrón Dispose que habría manejado esto con la palabra clave using
.
Incluso sé mejor que un código C ++ hubiera sido algo así como (usando una API similar a Java):
void copyFile(FileOutputStream & oDStream, FileInputStream & oSStream)
{
BufferedInputStream oSBuffer(oSStream, 4096);
BufferedOutputStream oDBuffer(oDStream, 4096);
int c;
while((c = oSBuffer.read()) != -1) // could throw a IOException
{
oDBuffer.write(c); // could throw a IOException
}
// I don''t care about resources, as RAII handle them for me
}
Me falta algo, ¿o realmente tengo que producir un código feo e hinchado en Java solo para manejar excepciones en el método close()
de un Flujo Buffered?
(Por favor, dime que estoy equivocado en algún lugar ...)
EDITAR: ¿Soy yo o al actualizar esta página, vi tanto la pregunta como todas las respuestas disminuidas en un punto en un par de minutos? ¿Alguien se divierte demasiado mientras se mantiene en el anonimato?
EDIT 2: McDowell me ofreció un enlace muy interesante que sentí que tenía que mencionar aquí: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html
EDIT 3: Siguiendo el enlace de McDowell, me lancé sobre una propuesta para Java 7 de un patrón similar al patrón de uso de C #: http://tech.puredanger.com/java7/#resourceblock . Mi problema está explícitamente descrito. Aparentemente, incluso con el Java 7 do
, los problemas persisten.
Hay problemas, pero el código que encontraste en la web es realmente pobre.
Al cerrar las secuencias de almacenamiento intermedio, se cierra la secuencia que se encuentra debajo. Realmente no quieres hacer eso. Todo lo que quiere hacer es purgar el flujo de salida. Además, no tiene sentido especificar las secuencias subyacentes para los archivos. El rendimiento es malo porque está copiando un byte a la vez (en realidad, si utiliza el uso de java.io, puede usar transferTo / transferFrom, que es un poco más rápido). Mientras estamos a punto, los nombres de las variables apestan. Asi que:
public static void copy(
InputStream in, OutputStream out
) throw IOException {
byte[] buff = new byte[8192];
for (;;) {
int len = in.read(buff);
if (len == -1) {
break;
}
out.write(buff, 0, len);
}
}
Si te encuentras utilizando mucho try-finally, entonces puedes factorizarlo con el modismo "execute around".
En mi opinión: Java debería tener alguna manera de cerrar los recursos al final del alcance. Sugiero agregar private
como un operador de postfijo unario para cerrar al final del bloque adjunto.
Para tareas comunes de IO, como copiar un archivo, un código como el que se muestra arriba está reinventando la rueda. Desafortunadamente, el JDK no proporciona servicios de nivel superior, pero apache commons -io sí.
Por ejemplo, FileUtils contiene varios métodos de utilidad para trabajar con archivos y directorios (incluida la copia). Por otro lado, si realmente necesita usar el soporte IO en el JDK, IOUtils contiene un conjunto de métodos closeQuietly () que cierran Readers, Writers, Streams, etc. sin lanzar excepciones.
Sí, así es como funciona Java. Hay inversión de control: el usuario del objeto debe saber cómo limpiar el objeto en lugar de limpiarlo por sí mismo. Esto desafortunadamente lleva a una gran cantidad de código de limpieza dispersos a través de su código java.
C # tiene la palabra clave "usar" para llamar automáticamente a Dispose cuando un objeto sale del alcance. Java no tiene tal cosa.
El patrón try / finally es la forma correcta de manejar flujos en la mayoría de los casos para Java 6 y versiones anteriores.
Algunos defienden el cierre silencioso de las transmisiones. Tenga cuidado al hacer esto por estas razones: Java: cómo no hacer un lío de manejo de flujo
Java 7 presenta try-with-resources :
/** transcodes text file from one encoding to another */
public static void transcode(File source, Charset srcEncoding,
File target, Charset tgtEncoding)
throws IOException {
try (InputStream in = new FileInputStream(source);
Reader reader = new InputStreamReader(in, srcEncoding);
OutputStream out = new FileOutputStream(target);
Writer writer = new OutputStreamWriter(out, tgtEncoding)) {
char[] buffer = new char[1024];
int r;
while ((r = reader.read(buffer)) != -1) {
writer.write(buffer, 0, r);
}
}
}
AutoCloseable
tipos de AutoCloseable
se cerrarán automáticamente:
public class Foo {
public static void main(String[] args) {
class CloseTest implements AutoCloseable {
public void close() {
System.out.println("Close");
}
}
try (CloseTest closeable = new CloseTest()) {}
}
}