Perguntas e Respostas de Entrevista sobre Exceções em Java

Java fornece uma abordagem robusta e orientada a objetos para lidar com cenários de exceção conhecida como Tratamento de Exceção em Java. Algum tempo atrás, escrevi um longo post sobre Tratamento de Exceção em Java e hoje estou listando algumas Questões Importantes de Exceções em Java com Respostas para ajudá-lo em entrevistas.

  1. O que é uma Exceção em Java?
  2. Quais são as Palavras-chave de Tratamento de Exceção em Java?
  3. Explique a Hierarquia de Exceção em Java?
  4. Quais são os métodos importantes da Classe de Exceção em Java?
  5. Explique a Funcionalidade ARM do Java 7 e o bloco multi-catch?
  6. Qual é a diferença entre Exceções Verificadas e Não Verificadas em Java?
  7. Qual é a diferença entre as palavras-chave throw e throws em Java?
  8. Como escrever exceções personalizadas em Java?
  9. O que é OutOfMemoryError em Java?
  10. Quais são os diferentes cenários que causam “Exceção na thread principal”?
  11. Qual é a diferença entre final, finally e finalize em Java?
  12. O que acontece quando uma exceção é lançada pelo método principal?
  13. Podemos ter um bloco catch vazio?
  14. Fornecer algumas Melhores Práticas de Manipulação de Exceção em Java?
  15. Qual é o problema com os programas abaixo e como podemos corrigi-los?

1. O que é uma Exceção em Java?

Uma exceção é um evento de erro que pode ocorrer durante a execução de um programa e interrompe seu fluxo normal. A exceção pode surgir de diferentes tipos de situações, como dados incorretos inseridos pelo usuário, falha de hardware, falha na conexão de rede, etc. Sempre que ocorrer algum erro durante a execução de uma instrução java, um objeto de exceção é criado, e então JRE tenta encontrar um manipulador de exceção para lidar com a exceção. Se um manipulador adequado for encontrado, então o objeto de exceção é passado para o código do manipulador para processar a exceção, conhecido como capturar a exceção. Se nenhum manipulador for encontrado, então a aplicação lança a exceção para o ambiente de tempo de execução e o JRE termina o programa. A estrutura de manipulação de exceção em Java é usada para lidar apenas com erros de tempo de execução, erros de compilação não são tratados pela estrutura de manipulação de exceção.

2. Quais são as palavras-chave de tratamento de exceção em Java?

Há quatro palavras-chave usadas no tratamento de exceções em Java.

  1. throw: Às vezes, queremos explicitamente criar um objeto de exceção e depois lançá-lo para interromper o processamento normal do programa. A palavra-chave throw é usada para lançar exceções para o tempo de execução para lidar com ela.
  2. throws: Quando estamos lançando uma exceção verificada em um método e não a estamos tratando, então precisamos usar a palavra-chave throws na assinatura do método para informar ao programa chamador as exceções que podem ser lançadas pelo método. O método chamador pode tratar essas exceções ou propagá-las para seu método chamador usando a palavra-chave throws. Podemos fornecer várias exceções na cláusula throws e ela pode ser usada com o método main() também.
  3. try-catch: Usamos o bloco try-catch para tratamento de exceções em nosso código. try é o início do bloco e catch está no final do bloco try para lidar com as exceções. Podemos ter vários blocos catch com um try e blocos try-catch também podem ser aninhados. O bloco catch requer um parâmetro que deve ser do tipo Exception.
  4. finalmente: O bloco finally é opcional e pode ser usado apenas com um bloco try-catch. Como uma exceção interrompe o processo de execução, pode ser que tenhamos alguns recursos abertos que não serão fechados, então podemos usar o bloco finally. O bloco finally é executado sempre, independentemente de ocorrer uma exceção ou não.

3. Explique a Hierarquia de Exceções do Java?

As exceções Java são hierárquicas e a herança é usada para categorizar diferentes tipos de exceções. `Throwable` é a classe pai da Hierarquia de Exceções Java e possui dois objetos filhos – `Error` e `Exception`. As exceções são ainda divididas em exceções verificadas e exceções de tempo de execução. Os erros são cenários excepcionais que estão fora do escopo da aplicação e não é possível antecipar e recuperar deles, por exemplo, falha de hardware, falha no JVM ou erro de falta de memória. As exceções verificadas são cenários excepcionais que podemos antecipar em um programa e tentar nos recuperar deles, por exemplo, FileNotFoundException. Devemos capturar essa exceção e fornecer uma mensagem útil ao usuário e registrá-la adequadamente para fins de depuração. `Exception` é a classe pai de todas as Exceções Verificadas. As Exceções de Tempo de Execução são causadas por programação ruim, por exemplo, tentar recuperar um elemento do Array. Devemos verificar o comprimento do array primeiro antes de tentar recuperar o elemento, caso contrário, pode lançar `ArrayIndexOutOfBoundsException` durante a execução. `RuntimeException` é a classe pai de todas as exceções de tempo de execução.

4. Quais são os métodos importantes da classe Java Exception?

Exception e todas as suas subclasses não fornecem métodos específicos, e todos os métodos são definidos na classe base Throwable.

  1. String getMessage() – Este método retorna a mensagem String de Throwable e a mensagem pode ser fornecida ao criar a exceção por meio de seu construtor.
  2. String getLocalizedMessage() – Este método é fornecido para que as subclasses possam substituí-lo e fornecer mensagens específicas para o local ao programa chamador. A implementação da classe Throwable deste método simplesmente usa o método getMessage() para retornar a mensagem de exceção.
  3. synchronized Throwable getCause() – Este método retorna a causa da exceção ou null se a causa for desconhecida.
  4. String toString() – Este método retorna as informações sobre Throwable no formato de String. A String retornada contém o nome da classe Throwable e a mensagem localizada.
  5. void printStackTrace() – Este método imprime as informações de rastreamento de pilha no fluxo de erro padrão. Este método é sobrecarregado e podemos passar PrintStream ou PrintWriter como argumento para escrever as informações de rastreamento de pilha no arquivo ou fluxo.

Explicação do Recurso ARM do Java 7 e bloco multi-catch?

Se você estiver capturando muitas exceções em um único bloco try, você notará que o código do bloco catch parece muito feio e geralmente consiste em código redundante para registrar o erro, tendo isso em mente, uma das características do Java 7 foi o bloco multi-catch onde podemos capturar várias exceções em um único bloco catch. O bloco catch com este recurso parece o seguinte:

catch(IOException | SQLException | Exception ex){
     logger.error(ex);
     throw new MyException(ex.getMessage());
}

Muitas vezes, usamos o bloco finally apenas para fechar os recursos e às vezes esquecemos de fechá-los e obtemos exceções em tempo de execução quando os recursos são esgotados. Essas exceções são difíceis de depurar e talvez seja necessário verificar cada lugar onde estamos usando esse tipo de recurso para garantir que estamos fechando-o. Então, uma das melhorias do Java 7 foi o try-with-resources onde podemos criar um recurso na própria instrução try e usá-lo dentro do bloco try-catch. Quando a execução sai do bloco try-catch, o ambiente de tempo de execução fecha automaticamente esses recursos. Exemplo de bloco try-catch com essa melhoria é:

try (MyResource mr = new MyResource()) {
            System.out.println("MyResource created in try-with-resources");
        } catch (Exception e) {
            e.printStackTrace();
        }

Leia mais sobre isso em Java 7 ARM.

6. Qual é a diferença entre Exceções Verificadas e Não Verificadas em Java?

  1. Exceções Verificadas devem ser tratadas no código usando o bloco try-catch ou então o método deve usar a palavra-chave throws para informar ao chamador sobre as exceções verificadas que podem ser lançadas pelo método. Exceções Não Verificadas não precisam ser tratadas no programa ou mencionadas na cláusula throws do método.
  2. Exception é a superclasse de todas as exceções verificadas, enquanto RuntimeException é a superclasse de todas as exceções não verificadas. Note que RuntimeException é a subclasse de Exception.
  3. Exceções verificadas são cenários de erro que precisam ser tratados no código, caso contrário você receberá um erro de compilação. Por exemplo, se você usar FileReader para ler um arquivo, ele lança FileNotFoundException e devemos capturá-lo no bloco try-catch ou lançá-lo novamente para o método chamador. Exceções não verificadas são geralmente causadas por má programação, por exemplo, NullPointerException ao invocar um método em uma referência de objeto sem garantir que ela não seja nula. Por exemplo, posso escrever um método para remover todas as vogais da string. É responsabilidade do chamador garantir que não passe uma string nula. Eu posso alterar o método para lidar com esses cenários, mas idealmente, o chamador deve cuidar disso.

7. Qual é a diferença entre as palavras-chave throw e throws em Java?

A palavra-chave throws é usada na assinatura do método para declarar as exceções que o método pode lançar, enquanto a palavra-chave throw é usada para interromper o fluxo do programa e entregar o objeto de exceção ao tempo de execução para lidar com ele.

8. Como escrever exceções personalizadas em Java?

Podemos estender a classe Exception ou qualquer uma de suas subclasses para criar nossa própria classe de exceção personalizada. A classe de exceção personalizada pode ter suas próprias variáveis e métodos que podemos usar para passar códigos de erro ou outras informações relacionadas à exceção para o manipulador de exceção. Um exemplo simples de uma exceção personalizada é mostrado abaixo.

package com.journaldev.exceptions;

import java.io.IOException;

public class MyException extends IOException {

	private static final long serialVersionUID = 4664456874499611218L;
	
	private String errorCode="Unknown_Exception";
	
	public MyException(String message, String errorCode){
		super(message);
		this.errorCode=errorCode;
	}
	
	public String getErrorCode(){
		return this.errorCode;
	}
	

}

9. O que é OutOfMemoryError em Java?

OutOfMemoryError no Java é uma subclasse de java.lang.VirtualMachineError e é lançado pela JVM quando ela fica sem memória heap. Podemos corrigir esse erro fornecendo mais memória para executar a aplicação Java através das opções java. $>java MyProgram -Xms1024m -Xmx1024m -XX:PermSize=64M -XX:MaxPermSize=256m

10. Quais são os diferentes cenários que causam “Exception in thread main”?

Alguns dos cenários comuns de exceção na thread principal são:

  • Exception in thread main java.lang.UnsupportedClassVersionError: Essa exceção ocorre quando sua classe Java é compilada a partir de outra versão do JDK e você está tentando executá-la a partir de outra versão do Java.
  • Exception in thread main java.lang.NoClassDefFoundError: Existem duas variantes desta exceção. A primeira é quando você fornece o nome completo da classe com a extensão .class. O segundo cenário é quando a classe não é encontrada.
  • Exception in thread main java.lang.NoSuchMethodError: main: Essa exceção ocorre quando você está tentando executar uma classe que não possui o método main.
  • Exceção na thread “main” java.lang.ArithmeticException: Sempre que uma exceção é lançada pelo método principal, ela é impressa no console. A primeira parte explica que uma exceção é lançada pelo método principal, a segunda parte imprime o nome da classe da exceção e depois de dois pontos, imprime a mensagem da exceção.

Leia mais sobre isso em Java Exceção na thread principal.

11. Qual é a diferença entre final, finally e finalize em Java?

final e finally são palavras-chave em Java, enquanto finalize é um método. A palavra-chave final pode ser usada com variáveis de classe para que não possam ser reatribuídas, com a classe para evitar extensão por outras classes e com métodos para evitar substituição por subclasses. A palavra-chave finally é usada com blocos try-catch para fornecer declarações que sempre serão executadas mesmo se alguma exceção surgir, geralmente finally é usado para fechar recursos. O método finalize() é executado pelo Coletor de Lixo antes que o objeto seja destruído, é uma ótima maneira de garantir que todos os recursos globais sejam fechados. Dos três, apenas finally está relacionado ao tratamento de exceções em Java.

12. O que acontece quando uma exceção é lançada pelo método principal?

Quando uma exceção é lançada pelo método main(), o tempo de execução do Java encerra o programa e imprime a mensagem de exceção e a pilha de chamadas no console do sistema.

13. Podemos ter um bloco catch vazio?

Podemos ter um bloco catch vazio, mas é um exemplo de má programação. Nunca devemos ter um bloco catch vazio, pois se a exceção for capturada por esse bloco, não teremos informações sobre a exceção e será um pesadelo para depurá-la. Deve haver pelo menos uma instrução de registro para registrar os detalhes da exceção no console ou nos arquivos de log.

14. Forneça algumas boas práticas de manipulação de exceções em Java?

Algumas das melhores práticas relacionadas à manipulação de exceções em Java são:

  • Use Exceções Específicas para facilitar a depuração.
  • Lance Exceções Cedo (Falha-Rápido) no programa.
  • Capture Exceções tarde no programa, deixe o chamador lidar com a exceção.
  • Use o recurso Java 7 ARM para garantir que os recursos sejam fechados ou use o bloco finally para fechá-los corretamente.
  • Sempre registre mensagens de exceção para fins de depuração.
  • Use bloco multi-catch para fechar de forma mais limpa.
  • Use exceções personalizadas para lançar um único tipo de exceção a partir da API de sua aplicação.
  • Siga a convenção de nomenclatura, sempre termine com Exception.
  • Documente as Exceções lançadas por um método usando @throws no javadoc.
  • Exceções são custosas, então lance-as apenas quando fizer sentido. Caso contrário, você pode capturá-las e fornecer uma resposta nula ou vazia.

Leia mais sobre elas em detalhes em Práticas recomendadas para tratamento de exceções em Java.

15. Qual é o problema com os programas abaixo e como podemos corrigi-los?

Nesta seção, vamos examinar algumas questões de programação relacionadas a exceções em Java.

  1. Qual é o problema com o programa abaixo?

    package com.journaldev.exceptions;
    
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    public class TestException {
    
    	public static void main(String[] args) {
    		try {
    			testExceptions();
    		} catch (FileNotFoundException | IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	
    	
    	public static void testExceptions() throws IOException, FileNotFoundException{
    		
    	}
    }
    

    O programa acima não irá compilar e você receberá uma mensagem de erro “A exceção FileNotFoundException já está capturada pela alternativa IOException”. Isso ocorre porque FileNotFoundException é uma subclasse de IOException, existem duas maneiras de resolver esse problema. A primeira maneira é usar um único bloco catch para ambas as exceções.

    		try {
    			testExceptions();
    		}catch(FileNotFoundException e){
    			e.printStackTrace();
    		}catch (IOException  e) {
    			e.printStackTrace();
    		}
    

    Outra maneira é remover o FileNotFoundException do bloco de multi-captura.

    		try {
    			testExceptions();
    		}catch (IOException  e) {
    			e.printStackTrace();
    		}
    

    Você pode escolher qualquer uma dessas abordagens com base no código do seu bloco catch.

  2. Qual é o problema com o programa abaixo?

    package com.journaldev.exceptions;
    
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    import javax.xml.bind.JAXBException;
    
    public class TestException1 {
    
    	public static void main(String[] args) {
    			try {
    				go();
    			} catch (IOException e) {
    				e.printStackTrace();
    			} catch (FileNotFoundException e) {
    				e.printStackTrace();
    			} catch (JAXBException e) {
    				e.printStackTrace();
    			}
    	}
    
    	public static void go() throws IOException, JAXBException, FileNotFoundException{
    		
    	}
    }
    

    O programa não irá compilar porque FileNotFoundException é uma subclasse de IOException, então o bloco catch de FileNotFoundException é inalcançável e você receberá uma mensagem de erro como “Bloco catch inacessível para FileNotFoundException. Já está tratado pelo bloco catch para IOException”. Você precisa corrigir a ordem dos blocos catch para resolver esse problema.

    			try {
    				go();
    			} catch (FileNotFoundException e) {
    				e.printStackTrace();
    			} catch (IOException e) {
    				e.printStackTrace();
    			} catch (JAXBException e) {
    				e.printStackTrace();
    			}
    

    Observe que JAXBException não está relacionado a IOException ou FileNotFoundException e pode ser colocado em qualquer lugar na hierarquia de blocos catch acima.

  3. Qual é o problema com o programa abaixo?

    package com.journaldev.exceptions;
    
    import java.io.IOException;
    
    import javax.xml.bind.JAXBException;
    
    public class TestException2 {
    
    	public static void main(String[] args) {
    		try {
    			foo();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}catch(JAXBException e){
    			e.printStackTrace();
    		}catch(NullPointerException e){
    			e.printStackTrace();
    		}catch(Exception e){
    			e.printStackTrace();
    		}
    	}
    
    	public static void foo() throws IOException{
    		
    	}
    }
    

    O programa não vai compilar porque JAXBException é uma exceção verificada e o método foo() deveria lançar essa exceção para ser capturada no método chamador. Você receberá uma mensagem de erro como “Bloco catch inacessível para JAXBException. Essa exceção nunca é lançada do corpo da declaração try”. Para resolver esse problema, você terá que remover o bloco catch de JAXBException. Observe que capturar NullPointerException é válido porque é uma exceção não verificada.

  4. Qual é o problema com o programa abaixo?

    pacote com.journaldev.exceptions;
    
    public class TestException3 {
    
    	public static void main(String[] args) {
    		tente {
    		bar();
    		} capture(NullPointerException e){
    			e.printStackTrace();
    		} capture(Exception e){
    			e.printStackTrace();
    		}
    		
    		foo();
    	}
    
    	public static void bar(){
    		
    	}
    	
    	public static void foo() throws NullPointerException{
    		
    	}
    }
    

    Esta é uma pergunta de pegadinha, não há problema com o código e ele irá compilar com sucesso. Sempre podemos capturar uma Exception ou qualquer exceção não verificada, mesmo que não esteja na cláusula throws do método. Da mesma forma, se um método (foo) declarar uma exceção não verificada na cláusula throws, não é obrigatório lidar com isso no programa.

  5. Qual é o problema com o programa abaixo?

    package com.journaldev.exceptions;
    
    import java.io.IOException;
    
    public class TestException4 {
    
    	public void start() throws IOException{		
    	}
    	
    	public void foo() throws NullPointerException{
    		
    	}
    }
    
    class TestException5 extends TestException4{
    	
    	public void start() throws Exception{
    	}
    	
    	public void foo() throws RuntimeException{
    		
    	}
    }
    

    O programa acima não vai compilar porque a assinatura do método start() não é a mesma na subclasse. Para corrigir esse problema, podemos alterar a assinatura do método na subclasse para ser exatamente a mesma da superclasse, ou podemos remover a cláusula throws do método da subclasse, conforme mostrado abaixo.

    @Override
    	public void start(){
    	}
    
  6. Qual é o problema com o programa abaixo?

    package com.journaldev.exceptions;
    
    import java.io.IOException;
    
    import javax.xml.bind.JAXBException;
    
    public class TestException6 {
    
    	public static void main(String[] args) {
    		try {
    			foo();
    		} catch (IOException | JAXBException e) {
    			e = new Exception("");
    			e.printStackTrace();
    		}catch(Exception e){
    			e = new Exception("");
    			e.printStackTrace();
    		}
    	}
    
    	public static void foo() throws IOException, JAXBException{
    		
    	}
    }
    

    O programa acima não irá compilar porque o objeto de exceção no bloco multi-catch é final e não podemos alterar seu valor. Você receberá um erro de compilação como “O parâmetro e de um bloco multi-catch não pode ser atribuído”. Devemos remover a atribuição de “e” para um novo objeto de exceção para resolver este erro. Leia mais em Java 7 bloco multi-catch.

É tudo para as perguntas de entrevista sobre exceções em Java, espero que você goste delas. Estarei adicionando mais à lista no futuro, certifique-se de marcá-la para uso futuro.

Source:
https://www.digitalocean.com/community/tutorials/java-exception-interview-questions-and-answers