1. Recursos externos

À medida que um programa Java é executado, às vezes ele interage com entidades fora da máquina Java. Por exemplo, com arquivos em disco. Essas entidades são geralmente chamadas de recursos externos. Os recursos internos são os objetos criados dentro da máquina Java.

Normalmente, a interação segue este esquema:

Declaração de tentativa com recursos

Recursos de rastreamento

O sistema operacional monitora rigorosamente os recursos disponíveis e também controla o acesso compartilhado a eles de diferentes programas. Por exemplo, se um programa alterar um arquivo, outro programa não poderá alterar (ou excluir) esse arquivo. Esse princípio não se limita a arquivos, mas eles fornecem o exemplo mais facilmente compreensível.

O sistema operacional possui funções (APIs) que permitem a um programa adquirir e/ou liberar recursos. Se um recurso estiver ocupado, apenas o programa que o adquiriu poderá trabalhar com ele. Se um recurso é gratuito, qualquer programa pode adquiri-lo.

Imagine que seu escritório tenha canecas de café compartilhadas. Se alguém pega uma caneca, outras pessoas não podem mais pegá-la. Mas uma vez que a caneca é usada, lavada e colocada de volta em seu lugar, qualquer um pode pegá-la novamente. A situação com assentos em um ônibus ou metrô é a mesma. Se um assento estiver livre, qualquer pessoa pode ocupá-lo. Se um assento estiver ocupado, ele será controlado pela pessoa que o ocupou.

Aquisição de recursos externos .

Toda vez que seu programa Java começa a trabalhar com um arquivo em disco, a máquina Java solicita ao sistema operacional acesso exclusivo a ele. Se o recurso for gratuito, a máquina Java o adquirirá.

Mas depois que terminar de trabalhar com o arquivo, esse recurso (arquivo) deve ser liberado, ou seja, você precisa avisar ao sistema operacional que não precisa mais dele. Se você não fizer isso, o recurso continuará sendo mantido pelo seu programa.

O sistema operacional mantém uma lista de recursos ocupados por cada programa em execução. Se o seu programa exceder o limite de recursos atribuído, o sistema operacional não fornecerá mais novos recursos.

A boa notícia é que se o seu programa for encerrado, todos os recursos são liberados automaticamente (o próprio sistema operacional faz isso).

A má notícia é que, se você estiver escrevendo um aplicativo de servidor (e muitos aplicativos de servidor são escritos em Java), seu servidor precisa ser capaz de funcionar por dias, semanas e meses sem parar. E se você abrir 100 arquivos por dia e não fechá-los, em algumas semanas seu aplicativo atingirá o limite de recursos e travará. Isso está muito aquém dos meses de trabalho estável.


2. close()método

As classes que utilizam recursos externos possuem um método especial para liberá-los: close().

A seguir, fornecemos um exemplo de programa que grava algo em um arquivo e fecha o arquivo quando terminar, ou seja, libera os recursos do sistema operacional. Parece algo assim:

Código Observação
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
O caminho para o arquivo.
Obtenha o objeto de arquivo: adquira o recurso.
Escreva no arquivo
Feche o arquivo - libere o recurso

Depois de trabalhar com um arquivo (ou outros recursos externos), você deve chamar o close()método no objeto vinculado ao recurso externo.

Exceções

Tudo parece simples. Mas podem ocorrer exceções durante a execução de um programa e o recurso externo não será liberado. E isso é muito ruim.

Para garantir que o close()método seja sempre chamado, precisamos agrupar nosso código em um bloco try- e adicionar o método ao bloco. Vai parecer algo assim:catchfinallyclose()finally

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

Este código não irá compilar, porque a outputvariável é declarada dentro do try {}bloco e, portanto, não é visível no finallybloco.

Vamos consertar:

FileOutputStream output = new FileOutputStream(path);

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

Tudo bem, mas não funcionará se ocorrer um erro ao criar o FileOutputStreamobjeto, e isso pode acontecer com bastante facilidade.

Vamos consertar:

FileOutputStream output = null;

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

Ainda há algumas críticas. Primeiro, se ocorrer um erro ao criar o FileOutputStreamobjeto, a outputvariável será nula. Essa possibilidade deve ser contabilizada no finallybloco.

Em segundo lugar, o close()método é sempre chamado no finallybloco, o que significa que não é necessário no trybloco. O código final ficará assim:

FileOutputStream output = null;

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

Mesmo se não considerarmos o catchbloco, que pode ser omitido, nossas 3 linhas de código se tornam 10. Mas basicamente apenas abrimos o arquivo e escrevemos 1. Um pouco complicado, não acha?


3. try-com-recursos

E aqui os criadores de Java decidiram jogar um pouco de açúcar sintático em nós. A partir de sua 7ª versão, o Java tem uma nova tryinstrução -with-resources.

Ele foi criado justamente para resolver o problema da chamada obrigatória ao close()método. O caso geral parece bastante simples:

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

Esta é outra variação da try declaração . Você precisa adicionar parênteses após a trypalavra-chave e, em seguida, criar objetos com recursos externos dentro dos parênteses. Para cada objeto entre parênteses, o compilador adiciona uma finallyseção e uma chamada ao close()método.

Abaixo estão dois exemplos equivalentes:

código longo Código com try-with-resources
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);
}

O código que usa try-with-resources é muito mais curto e fácil de ler. E quanto menos código tivermos, menor a chance de cometer um erro de digitação ou outro erro.

A propósito, podemos adicionar catche finallybloquear à tryinstrução -with-resources. Ou você não pode adicioná-los se eles não forem necessários.



4. Várias variáveis ​​ao mesmo tempo

A propósito, muitas vezes você pode encontrar uma situação em que precisa abrir vários arquivos ao mesmo tempo. Digamos que você esteja copiando um arquivo, então você precisa de dois objetos: o arquivo do qual você está copiando os dados e o arquivo para o qual você está copiando os dados.

Nesse caso, a tryinstrução -with-resources permite criar um, mas vários objetos nela. O código que cria os objetos deve ser separado por ponto e vírgula. Aqui está a aparência geral desta declaração:

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

Exemplo de cópia de arquivos:

código longo Código curto
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);
}

Bem, o que podemos dizer aqui? try-com-recursos é uma coisa maravilhosa!


5. AutoCloseableinterface

Mas isso não é tudo. O leitor atento começará imediatamente a procurar armadilhas que limitem como essa afirmação pode ser aplicada.

Mas como a tryinstrução -with-resources funciona se a classe não tiver um close()método? Bem, suponha que nada será chamado. Sem método, sem problema.

Mas como a tryinstrução -with-resources funciona se a classe tiver vários close()métodos? E eles precisam que argumentos sejam passados ​​para eles? E a classe não tem um close()método sem parâmetros?

Espero que você realmente tenha se feito essas perguntas, e talvez ainda outras.

Para evitar tais problemas, os criadores do Java criaram uma interface especial chamada AutoCloseable, que possui apenas um método — close(), que não possui parâmetros.

Eles também adicionaram a restrição de que apenas objetos de classes que implementamAutoCloseable podem ser declarados como recursos em uma tryinstrução -with-resources. Como resultado, tais objetos sempre terão um close()método sem parâmetros.

A propósito, você acha que é possível para uma trydeclaração -with-resources declarar como um recurso um objeto cuja classe tem seu próprio close()método sem parâmetros, mas que não implementa AutoCloseable?

A má notícia: a resposta correta é não — as classes devem implementar a AutoCloseableinterface.

A boa notícia: Java tem muitas classes que implementam essa interface, então é muito provável que tudo funcione como deveria.