"¡Amigo, diez chozas!"

"¡Estoy feliz de aprender Java, Capitán!"

"Tranquilo, Amigo. Hoy tenemos un tema súper interesante. Hablaremos sobre cómo un programa Java interactúa con recursos externos y estudiaremos una declaración de Java muy interesante. Mejor no te tapes los oídos".

"Soy todo oídos."

"Cuando se ejecuta un programa Java, a veces interactúa con entidades fuera de la máquina Java. Por ejemplo, con archivos en el disco. Estas entidades generalmente se denominan recursos externos".

"Entonces, ¿qué se consideran recursos internos?"

"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

"El sistema operativo realiza un seguimiento riguroso de los recursos disponibles y también controla el acceso compartido a ellos desde diferentes programas. Por ejemplo, si un programa cambia un archivo, otro programa no puede cambiar (o eliminar) ese archivo. Este principio no es limitado a 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 está libre, cualquier programa puede adquirirlo". él.

"Imagínese que una oficina ha compartido tazas de café. 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 tomarla de nuevo".

"Entendido. Es como los asientos en el metro u otro transporte público. Si un asiento está libre, cualquiera puede tomarlo. Si un asiento está ocupado, entonces lo controla la persona que lo tomó".

"Así es. Y ahora hablemos de adquirir recursos externos . Cada vez que su programa Java comienza a trabajar con un archivo en el disco, la máquina Java le pide al sistema operativo acceso exclusivo a él. Si el recurso es gratuito, entonces la máquina Java adquiere él.

"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 lo hace, el recurso seguirá siendo sostenido por su programa".

"Suena justo."

"Para mantenerlo así, 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.

"Es como programas que pueden consumir toda la memoria..."

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

"Si esas son las buenas noticias, ¿significa que hay malas noticias?"

"Precisamente. La mala noticia es que si estás escribiendo una aplicación de servidor..."

"¿Pero escribo tales aplicaciones?"

"Muchas aplicaciones de servidor están escritas en Java, por lo que lo más probable es que las escriba para el trabajo. Como decía, si está escribiendo una aplicación de servidor, entonces su servidor necesita ejecutarse sin parar durante días, semanas, meses, etc."

"En otras palabras, el programa no termina, y eso significa que la memoria no se libera automáticamente".

"Exactamente. 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 lejos de meses de trabajo estable! ¿Qué se puede hacer?"

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

"Aquí hay 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 más o menos así:

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

"Ah... Entonces, después de trabajar con un archivo (u otros recursos externos), tengo que llamar al close()método en el objeto vinculado al recurso externo".

"Sí. Todo parece simple. Pero pueden ocurrir excepciones a medida que se ejecuta un programa, y ​​el recurso externo no se liberará".

"Y eso es muy malo. ¿Qué hacer?"

"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á así:

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

"Hmm... ¿Algo anda mal aquí?"

"Correcto. 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á todo bien ahora?"

"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();
}

"¿Y todo funciona ahora?"

"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.

"Segundo, 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".

"Uf... Es algo bueno que concluye el asunto. Relativamente comprensible, pero algo tedioso, ¿no?"

"Así es. Es por eso que los creadores de Java nos ayudaron agregando un poco de azúcar sintáctico. Ahora pasemos a lo más destacado del programa, o más bien, a esta lección:

try-con-recursos

"A partir de su séptima versión, Java tiene una nueva trydeclaración -con-recursos.

"Se creó precisamente para solucionar el problema de la llamada obligatoria al close()método".

"¡Suena prometedor!"

"El caso general parece bastante simple:

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

"¿Así que esta es otra variación de la try declaración ?"

"Sí. 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 Codifique 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);
}

"¡Genial! 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".

"Me alegro de que te guste. Por cierto, podemos agregar catchy finallybloques a la trydeclaración -con-recursos. O no puedes agregarlos si no son necesarios.

Varias variables al mismo tiempo

"A menudo puede encontrarse con una situación en la que necesita abrir varios archivos al mismo tiempo. Digamos 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 trydeclaració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 corto código largo
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);
}
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();
}

"Bueno, ¿qué podemos decir aquí? ¡ tryCon-recursos es algo maravilloso!"

"Lo que podemos decir es que debemos usarlo".