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:
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 |
---|---|
|
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:catch
finally
close()
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 output
variável é declarada dentro do try {}
bloco e, portanto, não é visível no finally
bloco.
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 FileOutputStream
objeto, 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 FileOutputStream
objeto, a output
variável será nula. Essa possibilidade deve ser contabilizada no finally
bloco.
Em segundo lugar, o close()
método é sempre chamado no finally
bloco, o que significa que não é necessário no try
bloco. 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 catch
bloco, 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 try
instruçã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 try
palavra-chave e, em seguida, criar objetos com recursos externos dentro dos parênteses. Para cada objeto entre parênteses, o compilador adiciona uma finally
seção e uma chamada ao close()
método.
Abaixo estão dois exemplos equivalentes:
código longo | Código com try-with-resources |
---|---|
|
|
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 catch
e finally
bloquear à try
instruçã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 try
instruçã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 |
---|---|
|
|
Bem, o que podemos dizer aqui? try
-com-recursos é uma coisa maravilhosa!
5. AutoCloseable
interface
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 try
instruçã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 try
instruçã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 try
instruçã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 try
declaraçã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 AutoCloseable
interface.
A boa notícia: Java tem muitas classes que implementam essa interface, então é muito provável que tudo funcione como deveria.
GO TO FULL VERSION