1. Recursos externos

Cuando se ejecuta un programa Java, a veces interactúa con entidades fuera de la máquina Java. Por ejemplo, con archivos en disco. Estas entidades suelen denominarse recursos externos. Los recursos internos son los objetos creados dentro de la máquina Java.

Normalmente, la interacción sigue este esquema:

Declaración de prueba con recursos

Seguimiento de recursos

El sistema operativo realiza un seguimiento riguroso de los recursos disponibles, y también controla el acceso compartido a los mismos desde diferentes programas. Por ejemplo, si un programa cambia un archivo, otro programa no puede cambiar (o eliminar) ese archivo. Este principio no se limita a los archivos, pero proporcionan el ejemplo más fácilmente comprensible.

El sistema operativo tiene funciones (API) que permiten que un programa adquiera y/o libere recursos. Si un recurso está ocupado, solo el programa que lo adquirió puede trabajar con él. Si un recurso es gratuito, cualquier programa puede adquirirlo.

Imagina que tu oficina tiene tazas de café compartidas. Si alguien toma una taza, entonces otras personas ya no pueden tomarla. Pero una vez que la taza se usa, se lava y se vuelve a colocar en su lugar, cualquiera puede volver a tomarla. La situación con los asientos en un autobús o metro es la misma. Si un asiento está libre, cualquiera puede tomarlo. Si un asiento está ocupado, entonces lo controla la persona que lo tomó.

Adquisición de recursos externos .

Cada vez que su programa Java comienza a trabajar con un archivo en el disco, la máquina Java solicita acceso exclusivo al sistema operativo. Si el recurso es gratuito, la máquina Java lo adquiere.

Pero una vez que haya terminado de trabajar con el archivo, este recurso (archivo) debe liberarse, es decir, debe notificar al sistema operativo que ya no lo necesita. Si no hace esto, entonces el recurso seguirá siendo retenido por su programa.

El sistema operativo mantiene una lista de recursos ocupados por cada programa en ejecución. Si su programa excede el límite de recursos asignado, entonces el sistema operativo ya no le dará nuevos recursos.

La buena noticia es que si su programa finaliza, todos los recursos se liberan automáticamente (el propio sistema operativo hace esto).

La mala noticia es que si está escribiendo una aplicación de servidor (y muchas aplicaciones de servidor están escritas en Java), su servidor debe poder ejecutarse durante días, semanas y meses sin parar. Y si abre 100 archivos al día y no los cierra, en un par de semanas su aplicación alcanzará su límite de recursos y fallará. Eso está muy por debajo de los meses de trabajo estable.


2. close()método

Las clases que usan recursos externos tienen un método especial para liberarlos: close().

A continuación proporcionamos un ejemplo de un programa que escribe algo en un archivo y luego cierra el archivo cuando termina, es decir, libera los recursos del sistema operativo. Se ve algo como esto:

Código Nota
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
La ruta al archivo.
Obtener el objeto de archivo: adquirir el recurso.
Escribir en el archivo
Cerrar el archivo - liberar el recurso

Después de trabajar con un archivo (u otros recursos externos), debe llamar al close()método en el objeto vinculado al recurso externo.

Excepciones

Todo parece simple. Pero pueden ocurrir excepciones mientras se ejecuta un programa y el recurso externo no se liberará. Y eso es muy malo.

Para asegurarnos de que close()siempre se llame al método, debemos envolver nuestro código en un bloque try- catch- finallyy agregar el close()método al finallybloque. Se verá algo como esto:

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Este código no se compilará porque la outputvariable se declara dentro del try {}bloque y, por lo tanto, no es visible en el finallybloque.

Arreglemoslo:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Está bien, pero no funcionará si ocurre un error cuando creamos el FileOutputStreamobjeto, y esto podría suceder con bastante facilidad.

Arreglemoslo:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Todavía hay algunas críticas. Primero, si ocurre un error al crear el FileOutputStreamobjeto, entonces la outputvariable será nula. Esta posibilidad debe tenerse en cuenta en el finallybloque.

En segundo lugar, el close()método siempre se llama en el finallybloque, lo que significa que no es necesario en el trybloque. El código final se verá así:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

Incluso si no consideramos el catchbloque, que se puede omitir, nuestras 3 líneas de código se convierten en 10. Pero básicamente abrimos el archivo y escribimos 1. Un poco engorroso, ¿no crees?


3. try-con-recursos

Y aquí los creadores de Java decidieron rociarnos un poco de azúcar sintáctico. A partir de su séptima versión, Java tiene una nueva trydeclaración -with-resources.

Fue creado precisamente para resolver el problema con la llamada obligatoria al close()método. El caso general parece bastante simple:

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

Esta es otra variación de la try declaración . Debe agregar paréntesis después de la trypalabra clave y luego crear objetos con recursos externos dentro de los paréntesis. Para cada objeto entre paréntesis, el compilador agrega una finallysección y una llamada al close()método.

A continuación se muestran dos ejemplos equivalentes:

código largo Código con probar con recursos
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

El código que usa try-with-resources es mucho más corto y más fácil de leer. Y cuanto menos código tengamos, menos posibilidades de cometer un error tipográfico u otro error.

Por cierto, podemos agregar catchy finallybloques a la tryinstrucción -with-resources. O no puede agregarlos si no son necesarios.



4. Varias variables a la vez

Por cierto, a menudo puede encontrarse con una situación en la que necesita abrir varios archivos al mismo tiempo. Supongamos que está copiando un archivo, por lo que necesita dos objetos: el archivo del que está copiando datos y el archivo al que está copiando datos.

En este caso, la tryinstrucción -with-resources le permite crear uno pero varios objetos en ella. El código que crea los objetos debe estar separado por punto y coma. Aquí está la apariencia general de esta declaración:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

Ejemplo de copia de archivos:

código largo Código corto
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

Bueno, ¿qué podemos decir aquí? try-con-recursos es una cosa maravillosa!


5. AutoCloseableinterfaz

Pero eso no es todo. El lector atento inmediatamente comenzará a buscar trampas que limitan cómo se puede aplicar esta afirmación.

Pero, ¿cómo funciona la trydeclaración -with-resources si la clase no tiene un close()método? Bueno, supongamos que no se llamará nada. Sin método, sin problema.

Pero, ¿cómo tryfunciona la declaración -with-resources si la clase tiene varios close()métodos? ¿Y necesitan que se les pasen argumentos? ¿ Y la clase no tiene un close()método sin parámetros?

Espero que realmente te hayas hecho estas preguntas, y quizás otras más.

Para evitar tales problemas, los creadores de Java idearon una interfaz especial llamada AutoCloseable, que tiene un solo método close(), que no tiene parámetros.

También agregaron la restricción de que solo los objetos de las clases que implementanAutoCloseable pueden declararse como recursos en una trydeclaración con recursos. Como resultado, dichos objetos siempre tendrán un close()método sin parámetros.

Por cierto, ¿crees que es posible que una trydeclaración con recursos declare como recurso un objeto cuya clase tiene su propio close()método sin parámetros pero que no implementa AutoCloseable?

Las malas noticias: la respuesta correcta es no: las clases deben implementar la AutoCloseableinterfaz.

La buena noticia: Java tiene muchas clases que implementan esta interfaz, por lo que es muy probable que todo funcione como debería.