Exceções

Em Java, “um evento que ocorre durante a execução de um programa que interrompe o fluxo normal de instruções” é chamado de exceção. Geralmente, é um evento inesperado ou indesejado que pode ocorrer em tempo de compilação ou em tempo de execução no código da aplicação. As exceções Java podem ser de vários tipos e todos os tipos de exceção são organizados em uma hierarquia fundamental.

Hierarquia de exceções em Java

A hierarquia de exceções em Java é organizada da seguinte forma:

  1. Throwable: A classe raiz de todas as exceções e erros em Java. Ela tem duas subclasses principais:
    • Exception: Representa problemas que um programa pode querer capturar e tratar.
    • Error: Representa problemas graves que normalmente o programa não deve tentar capturar (por exemplo, OutOfMemoryError).
  2. Tipos de Exception:
    • Exceções Verificadas (Checked Exceptions): Exceções que devem ser tratadas pelo programa, usando blocos try-catch ou declarando com throws. São verificadas pelo compilador. Exemplo: IOException, SQLException.
    • Exceções Não Verificadas (Unchecked Exceptions): Exceções que não são verificadas pelo compilador. São subclasses de RuntimeException, como NullPointerException, ArrayIndexOutOfBoundsException.

Exceções verificadas e não verificadas

Exceções verificadas (checked exceptions)

  • São verificadas em tempo de compilação.
  • O programador é obrigado a tratá-las ou declará-las com throws.
  • Exemplo: IOException, SQLException.

Exceções não verificadas (unchecked exceptions)

  • São verificadas em tempo de execução.
  • O programador não é obrigado a tratá-las.
  • Exemplo: NullPointerException, ArithmeticException.

Tratamento de exceções

O tratamento de exceções em Java é realizado usando os blocos trycatchfinally e throw.

Sintaxe:

try {
   //Bloco de código que pode gerar uma exceção
} catch (TipoExcecao e){
   //Bloco de código que trata a exceção
} [finally {
   //Bloco de código que será sempre executado
}]

O bloco try-catch-finally pode tratar uma ou várias exceções, declarando um bloco catch para cada exceção tratada.

O bloco finally é opcional e, se existir, só pode ocorrer uma vez, no final do bloco try-catch. Este bloco é sempre executado quer haja uma exceção quer não. Normalmente, este bloco é usado para libertar recursos, como por exemplo, fechar ficheiros ou conexões a uma base de dados.

Exemplo:

import java.io.*;

public class ReadFile {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("ficheiro.txt"));
            String line = reader.readLine();
            System.out.println(line);
        } catch (IOException e) {
            System.out.println("Erro ao ler o ficheiro.");
        } finally {
            try {
                if (reader != null) {
                    reader.close(); // O recurso é sempre fechado, mesmo se ocorrer uma exceção
                }
            } catch (IOException e) {
                System.out.println("Erro ao fechar o ficheiro.");
            }
        }
    }
}

Declarar exceções com throws

Quando, num bloco de código de um método, houver a eventualidade de esse código gerar uma exceção, o método pode fazer uma de duas coisas:

  • Tratar a exceção com try-catch.
  • Declarar a exceção com a palavra reservada throws de forma a que o código que chama o método trate a exceção:

A sintaxe de throws é a seguinte:

public nomeMetodo() throws TipoExcecao1[,..., TipoExcecaoN] {
   //código que pode gerar uma exceção
}

Exemplo:

public void lerFicheiro() throws IOException {
   BufferedReader reader = new BufferedReader(new FileReader("ficheiro.txt"));            
   String line = reader.readLine();            
   System.out.println(line);
}

public void mostraDadosFicheiro(){
   try{
      lerFicheiro();
   catch(IOException ex){
      System.out.println("Erro na leitura do ficheiro");
   }
}

No exemplo acima, o método lerFicheiro() não trata a exceção IOException mas “lança-a” para o método que o chamou. O método mostraDadosFicheiro() pode tratar a exceção ou “lança-la” a para o método que o chama.

Exceções personalizadas

O programador pode criar as suas próprias exceções. Fá-lo criando uma classe que seja subclasse de Exception. Depois, usa a instrução throw para disparar a exceção :

class NotaInvalidaException extends Exception {
    public NotaInvalidaException(String msg) {
        super(msg); //passa a mensagem para o construtor de Exception
    }
}

class Main {
   public validaNota(int nota){
      if(nota < 0 || nota > 20 ){
         throw new NotaInvalidaException("Nota inválida: deve ser entre 0 e 20");
      }
   }

   public void obterNota() {
      try{
         validaNota(17);
      }catch(NotaInvalidaException ex){
         System.out.println(ex.getMessage());
      }
   }
}