O FileInputStream(String filename) construtor cria um fluxo de entrada para o arquivo identificado por filename. Este construtor lança FileNotFoundException quando o arquivo não existe, se refere a um diretório ou ocorre outro problema relacionado. O FileOutputStream(String filename) construtor cria um fluxo de saída para o arquivo identificado por filename. Ele joga FileNotFoundException quando o arquivo existe, mas faz referência a um diretório, não existe e não pode ser criado, ou ocorre outro problema relacionado.

FileInputStream fornece um int read() método para ler um byte e retorná-lo como um inteiro de 32 bits. Este método retorna -1 no fim do arquivo. FileOutputStream fornece um void write(int b) método para escrever o byte nos 8 bits inferiores de b. Qualquer método lança IOException quando algo dá errado.

A maior parte do exemplo é um while loop que repetidamente read()é o próximo byte do fluxo de entrada e write()s que byte para o fluxo de saída, até read() sinaliza fim de arquivo.

O try A lógica de cópia de arquivo do bloco é fácil de seguir porque essa lógica não é combinada com código de verificação de exceção (if testes e relacionados throw declarações ocultas nos construtores e métodos), código de tratamento de exceções (que é executado em um ou mais associados catch blocos) e código de limpeza (para fechar os arquivos de origem e destino; este código é relegado a um associado finally bloco). Em contraste, a falta de uma estrutura orientada a exceções semelhante em C resulta em um código mais detalhado, conforme ilustrado pelo seguinte trecho de um C maior cp aplicativo (no arquivo de código deste artigo) que copia um arquivo de origem para um arquivo de destino:

if ((fpsrc = fopen(argv(1), "rb")) == NULL)
{
   fprintf(stderr, "unable to open %s for readingn", argv(1));
   return;
}

if ((fpdst = fopen(argv(2), "wb")) == NULL)
{
   fprintf(stderr, "unable to open %s for writingn", argv(1));
   fclose(fpsrc);
   return;
}

while ((c = fgetc(fpsrc)) != EOF)
   if (fputc(c, fpdst) == EOF)
   {
      fprintf(stderr, "unable to write to %sn", argv(1));
      break;
   }

Neste exemplo, a lógica de cópia de arquivo é mais difícil de seguir porque a lógica é misturada com verificação de exceção, tratamento de exceção e código de limpeza:

  • Os dois == NULL e um == EOF os cheques são o equivalente ao oculto throw declarações e cheques relacionados.
  • Os três fprintf() chamadas de função são o código de tratamento de exceções cujo equivalente Java seria executado em um ou mais catch blocos.
  • O fclose(fpsrc); chamada de função é um código de limpeza cujo equivalente Java seria executado em um finally bloquear.

Usando blocos catch para capturar exceções

A capacidade de tratamento de exceções do Java é baseada em catch blocos. Esta seção apresenta catch e vários catch blocos.

O bloco de captura

Java fornece o catch bloco para delimitar uma sequência de instruções que manipulam uma exceção. Um catch bloco tem a seguinte sintaxe:

catch (throwableType throwableObject)
{
   // one or more statements that handle an exception
}

O catch block é semelhante a um construtor, pois tem uma lista de parâmetros. No entanto, essa lista consiste em apenas um parâmetro, que é um tipo lançável (Throwable ou uma de suas subclasses) seguido por um identificador para um objeto desse tipo.

Quando ocorre uma exceção, um throwable é criado e lançado para a JVM, que procura o mais próximo catch bloco cujo tipo de parâmetro corresponde diretamente ou é o supertipo do objeto throwable lançado. Quando encontra esse bloco, a JVM passa o throwable para o parâmetro e executa o catch instruções do bloco, que podem interrogar o throwable passado e, de outra forma, manipular a exceção. Considere o seguinte exemplo:

catch (FileNotFoundException fnfe)
{
   System.err.println(fnfe.getMessage());
}

Este exemplo (que estende o anterior try exemplo de bloco) descreve um catch bloco que captura e manipula objetos arremessáveis ​​do tipo FileNotFoundException. Somente itens lançáveis ​​que correspondem a este tipo ou subtipo são capturados por este bloco.

Suponha que o FileInputStream(String filename) construtor joga FileNotFoundException. A JVM verifica o catch bloco seguinte try para ver se o tipo do parâmetro corresponde ao tipo lançável. Ao detectar uma correspondência, a JVM passa a referência do lançável para fnfe e transfere a execução para o bloco. O bloco responde invocando getMessage() para recuperar a mensagem da exceção, que então é gerada.

Especificando vários blocos catch

Você pode especificar vários catch blocos depois de um try bloco. Por exemplo, considere este trecho maior do mencionado Copy aplicativo:

FileInputStream fis = null;
FileOutputStream fos = null;
{
   fis = new FileInputStream(args(0));
   fos = new FileOutputStream(args(1));
   int c;
   while ((c = fis.read()) != -1)
      fos.write(c);
}
catch (FileNotFoundException fnfe)
{
   System.err.println(fnfe.getMessage());
}
catch (IOException ioe)
{
   System.err.println("I/O error: " + ioe.getMessage());
}

O primeiro catch alças de bloco FileNotFoundExceptions lançados de qualquer construtor. O segundo catch alças de bloco IOExceptioné jogado do read() e write() métodos.

Ao especificar vários catch blocos, não especifique um catch bloco com um supertipo antes de um catch bloco com um subtipo. Por exemplo, não coloque catch (IOException ioe) antes catch (FileNotFoundException fnfe). Se você fizer isso, o compilador relatará um erro porque catch
(IOException ioe)
também lidaria FileNotFoundExceptionareia catch (FileNotFoundException
fnfe)
nunca teria a chance de executar.

Da mesma forma, não especifique vários catch blocos com o mesmo tipo de arremesso. Por exemplo, não especifique dois catch (IOException ioe) {} blocos. Caso contrário, o compilador relata um erro.

Usando blocos finally para limpar exceções

Independentemente de uma exceção ser tratada ou não, você pode precisar executar tarefas de limpeza, como fechar um arquivo aberto. Java fornece o finally bloco para esta finalidade.

O finally bloco consiste em palavra-chave finally seguido por uma sequência de instruções delimitadas por chaves para executar. Pode aparecer após o final catch bloquear ou depois do try bloquear.

Limpeza em um contexto try-catch-finally

Quando os recursos devem ser limpos e uma exceção não está sendo lançada de um método, um finally o bloco é colocado após o final catch bloco. Isso é demonstrado pelo seguinte Copy trecho:

FileInputStream fis = null;
FileOutputStream fos = null;
try
{
   fis = new FileInputStream(args(0));
   fos = new FileOutputStream(args(1));
   int c;
   while ((c = fis.read()) != -1)
      fos.write(c);
}
catch (FileNotFoundException fnfe)
{
   System.err.println(fnfe.getMessage());
}
catch (IOException ioe)
{
   System.err.println("I/O error: " + ioe.getMessage());
}
finally
{
   if (fis != null)
      try
      {
         fis.close();
      }
      catch (IOException ioe)
      {
         // ignore exception
      }

   if (fos != null)
      try
      {
         fos.close();
      }
      catch (IOException ioe)
      {
         // ignore exception
      }
}

Se o try o bloco é executado sem exceção, a execução passa para o finally bloco para fechar os fluxos de entrada/saída do arquivo. Se uma exceção for lançada, o finally o bloco é executado após o apropriado catch bloquear.

FileInputStream e FileOutputStream herdar um void close() método que lança IOException quando o fluxo não pode ser fechado. Por esse motivo, envolvi cada um dos fis.close(); e fos.close(); em um try bloco. Eu deixei o associado catch bloco vazio para ilustrar o erro comum de ignorar uma exceção.

Um vazio catch bloco que é invocado com o throwable apropriado não tem como relatar a exceção. Você pode perder muito tempo rastreando a causa da exceção, apenas para descobrir que poderia tê-la detectado antes se o vazio catch block havia relatado a exceção, mesmo que apenas em um log.

Limpeza em um contexto de tentativa-final

Quando os recursos devem ser limpos e uma exceção está sendo lançada de um método, um finally o bloco é colocado após o try bloco: não há catch blocos. Considere o seguinte trecho de uma segunda versão do Copy aplicativo:

public static void main(String() args)
{
   if (args.length != 2)
   {
      System.err.println("usage: java Copy srcfile dstfile");
      return;
   }

   try
   {
      copy(args(0), args(1));
   }
   catch (IOException ioe)
   {
      System.err.println("I/O error: " + ioe.getMessage());
   }
}

static void copy(String srcFile, String dstFile) throws IOException
{
   FileInputStream fis = null;
   FileOutputStream fos = null;
   try
   {
      fis = new FileInputStream(srcFile);
      fos = new FileOutputStream(dstFile);
      int c;
      while ((c = fis.read()) != -1)
         fos.write(c);
   }
   finally
   {
      if (fis != null)
         try
         {
            fis.close();
         }
         catch (IOException ioe)
         {
            System.err.println(ioe.getMessage());
         }

      if (fos != null)
         try
         {
            fos.close();
         }
         catch (IOException ioe)
         {
            System.err.println(ioe.getMessage());
         }
   }
}

A lógica de cópia de arquivo foi movida para um copy() método. Este método é projetado para relatar uma exceção ao chamador, mas primeiro fecha cada arquivo aberto.

Este método é throws cláusula apenas lista IOException. Não é necessário incluir FileNotFoundException porque FileNotFoundException subclasses IOException.

Mais uma vez, o finally cláusula apresenta muito código apenas para fechar dois arquivos. Na segunda parte desta série, você aprenderá sobre a try-with-resources declaração, que elimina a necessidade de fechar explicitamente esses arquivos.

Para concluir

Este artigo apresentou a você os fundamentos do framework tradicional orientado a exceções do Java, mas há muito mais para entender. A segunda metade deste tutorial apresenta os recursos de linguagem orientados a exceções mais avançados do Java e os tipos de biblioteca, incluindo try-with-resources.