"Amigo, dez-cabana!"

"Estou feliz em aprender Java, capitão!"

"À vontade, amigo. Hoje temos um assunto superinteressante. Vamos falar sobre como um programa Java interage com recursos externos e vamos estudar uma instrução Java muito interessante. Melhor não tapar os ouvidos."

"Sou todo ouvidos."

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

"Então o que são considerados recursos internos?"

"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

"O sistema operacional controla rigorosamente os recursos disponíveis e também controla o acesso compartilhado a eles de diferentes programas. Por exemplo, se um programa altera um arquivo, outro programa não pode alterar (ou excluir) esse arquivo. Este princípio não é limitados a arquivos, mas 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, somente o programa que o adquiriu poderá trabalhar com ele. Se um recurso estiver livre, qualquer programa poderá adquiri-lo isto.

"Imagine que um escritório tenha xícaras 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 no lugar, qualquer pessoa pode pegá-la novamente."

"Entendi. É como assentos no metrô ou outro transporte público. Se um assento estiver livre, então qualquer um pode ocupá-lo. Se um assento estiver ocupado, então é controlado pela pessoa que o ocupou."

"Isso mesmo. E agora vamos falar sobre a 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 adquire isto.

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

"Isso parece justo."

"Para mantê-lo assim, 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.

"É como programas que podem consumir toda a memória..."

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

"Se essa é a boa notícia, isso significa que há más notícias?"

"Exatamente. A má notícia é que, se você estiver escrevendo um aplicativo de servidor..."

"Mas eu escrevo essas aplicações?"

"Muitos aplicativos de servidor são escritos em Java, portanto, provavelmente você os escreverá para o trabalho. Como eu estava dizendo, se você estiver escrevendo um aplicativo de servidor, seu servidor precisará funcionar ininterruptamente por dias, semanas, meses, etc."

"Em outras palavras, o programa não termina e isso significa que a memória não é liberada automaticamente."

"Exatamente. 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! O que pode ser feito?"

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

"Aqui está um exemplo de um programa que grava algo em um arquivo e, em seguida, fecha o arquivo quando terminar, ou seja, libera os recursos do sistema operacional. É mais ou menos 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

"Ah... Então, depois de trabalhar com um arquivo (ou outro recurso externo), tenho que chamar o close()método do objeto vinculado ao recurso externo."

"Sim. Tudo parece simples. Mas exceções podem ocorrer durante a execução de um programa e o recurso externo não será liberado."

"E isso é muito ruim. O que fazer?"

"Para garantir que o close()método seja sempre chamado, precisamos agrupar nosso código em um try- catch- finallybloco e adicionar o close()método ao finallybloco. Será mais ou menos assim:

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

"Hmm... Algo está errado aqui?"

"Certo. Este código não vai 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();
}

"Está tudo bem agora?"

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

"E tudo funciona agora?"

“Ainda existem algumas críticas. Primeiro, se ocorrer um erro na criação do FileOutputStreamobjeto, a outputvariável será nula. Essa possibilidade deve ser considerada 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 que não consideremos o catchbloco, que pode ser omitido, nossas 3 linhas de código se tornam 10. Mas basicamente apenas abrimos o arquivo e escrevemos 1."

"Ufa... É uma coisa boa que conclui o assunto. Relativamente compreensível, mas um tanto tedioso, não é?"

"Assim é. É por isso que os criadores do Java nos ajudaram adicionando um pouco de açúcar sintático. Agora vamos passar para o destaque do programa, ou melhor, esta lição:

try-com-recursos

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

"Parece promissor!"

"O caso geral parece bastante simples:

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

"Então esta é outra variação da try declaração ?"

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

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

"Estou feliz que você gostou. A propósito, podemos adicionar catche finallybloquear a trydeclaração -with-resources. Ou você não pode adicioná-los se eles não forem necessários.

Várias variáveis ​​ao mesmo tempo

"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 instruçã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 curto código longo
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();
}

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

"O que podemos dizer é que devemos usá-lo."