Perguntas sobre entrevistas de exceções em Java (+ respostas)

Perguntas sobre entrevistas de exceções em Java (+ respostas)

1. Visão geral

Exceções são um tópico essencial com o qual todo desenvolvedor Java deve estar familiarizado. Este artigo fornece respostas para algumas das perguntas que podem aparecer durante uma entrevista.

2. Questões

Q1. O que é uma exceção?

Uma exceção é um evento anormal que ocorre durante a execução de um programa e interrompe o fluxo normal das instruções do programa.

Q2. Qual é o objetivo das palavras-chave Throw and Throws?

A palavra-chavethrows é usada para especificar que um método pode lançar uma exceção durante sua execução. Aplica o tratamento explícito de exceções ao chamar um método:

public void simpleMethod() throws Exception {
    // ...
}

A palavra-chavethrow nos permite lançar um objeto de exceção para interromper o fluxo normal do programa. Isso é mais comumente usado quando um programa falha em satisfazer uma determinada condição:

if (task.isTooComplicated()) {
    throw new TooComplicatedException("The task is too complicated");
}

Q3. Como você pode lidar com uma exceção?

Usando uma declaraçãotry-catch-finally:

try {
    // ...
} catch (ExceptionType1 ex) {
    // ...
} catch (ExceptionType2 ex) {
    // ...
} finally {
    // ...
}

O bloco de código no qual uma exceção pode ocorrer está contido em um blocotry. Este bloco também é chamado de código "protegido" ou "guardado".

Se ocorrer uma exceção, o blococatch que corresponde à exceção sendo lançada é executado, caso contrário, todos os blocoscatch são ignorados.

O blocofinally é sempre executado após a saída do blocotry, se uma exceção foi lançada ou não dentro dele.

Q4. Como você pode capturar várias exceções?

Existem três maneiras de lidar com várias exceções em um bloco de código.

A primeira é usar um blococatch que pode lidar com todos os tipos de exceção sendo lançados:

try {
    // ...
} catch (Exception ex) {
    // ...
}

Lembre-se de que a prática recomendada é usar manipuladores de exceção o mais precisos possível.

Manipuladores de exceções que são muito amplos podem tornar seu código mais sujeito a erros, capturar exceções que não foram previstas e causar comportamento inesperado em seu programa.

A segunda maneira é implementar vários blocos de captura:

try {
    // ...
} catch (FileNotFoundException ex) {
    // ...
} catch (EOFException ex) {
    // ...
}

Observe que, se as exceções tiverem um relacionamento de herança; o tipo filho deve vir primeiro e o tipo pai depois. Se não fizermos isso, isso resultará em um erro de compilação.

O terceiro é usar um bloco de captura múltipla:

try {
    // ...
} catch (FileNotFoundException | EOFException ex) {
    // ...
}

Esse recurso, introduzido pela primeira vez no Java 7; reduz a duplicação de código e facilita a manutenção.

Q5. Qual é a diferença entre uma exceção marcada e uma exceção não verificada?

Uma exceção verificada deve ser tratada em um blocotry-catch ou declarada em uma cláusulathrows; ao passo que uma exceção não verificada não precisa ser tratada nem declarada.

As exceções marcadas e desmarcadas também são conhecidas como exceções em tempo de compilação e tempo de execução, respectivamente.

Todas as exceções são exceções verificadas, exceto aquelas indicadas porError,RuntimeException e suas subclasses.

Q6. Qual é a diferença entre uma exceção e um erro?

Uma exceção é um evento que representa uma condição da qual é possível recuperar, enquanto o erro representa uma situação externa geralmente impossível de recuperar.

Todos os erros lançados pela JVM são instâncias deError ou uma de suas subclasses, os mais comuns incluem, mas não estão limitados a:

  • OutOfMemoryError - lançado quando a JVM não pode alocar mais objetos porque está sem memória e o coletor de lixo não foi capaz de disponibilizar mais objetos

  • StackOverflowError - ocorre quando o espaço de pilha para um thread se esgota, normalmente porque um aplicativo recorre muito profundamente

  • ExceptionInInitializerError - sinaliza que uma exceção inesperada ocorreu durante a avaliação de um inicializador estático

  • NoClassDefFoundError - é lançado quando o carregador de classe tenta carregar a definição de uma classe e não consegue encontrá-la, geralmente porque os arquivosclass necessários não foram encontrados no caminho de classe

  • UnsupportedClassVersionError - ocorre quando a JVM tenta ler um arquivoclass e determina que a versão no arquivo não é compatível, normalmente porque o arquivo foi gerado com uma versão mais recente do Java

Embora um erro possa ser tratado com uma instruçãotry, essa não é uma prática recomendada, pois não há garantia de que o programa será capaz de fazer algo confiável depois que o erro foi lançado.

Q7. Que exceção será lançada ao executar o seguinte bloco de código?

Integer[][] ints = { { 1, 2, 3 }, { null }, { 7, 8, 9 } };
System.out.println("value = " + ints[1][1].intValue());

Ele lança umArrayIndexOutOfBoundsException, pois estamos tentando acessar uma posição maior do que o comprimento da matriz.

Q8. O que é o encadeamento de exceções?

Ocorre quando uma exceção é lançada em resposta a outra exceção. Isso nos permite descobrir o histórico completo do nosso problema levantado:

try {
    task.readConfigFile();
} catch (FileNotFoundException ex) {
    throw new TaskException("Could not perform task", ex);
}

Q9. O que é um Stacktrace e como ele se relaciona com uma exceção?

Um rastreamento de pilha fornece os nomes das classes e métodos que foram chamados, desde o início do aplicativo até o ponto em que ocorreu uma exceção.

É uma ferramenta de depuração muito útil, pois nos permite determinar exatamente onde a exceção foi lançada no aplicativo e as causas originais que levaram a ela.

Q10. Por que você deseja criar uma subclasse de uma exceção?

Se o tipo de exceção não for representado por aqueles que já existem na plataforma Java, ou se você precisar fornecer mais informações ao código do cliente para tratá-lo de maneira mais precisa, você deve criar uma exceção personalizada.

Decidir se uma exceção customizada deve ser verificada ou desmarcada depende inteiramente do caso de negócios. No entanto, como regra geral; se for esperado que o código que usa sua exceção se recupere, crie uma exceção verificada, caso contrário, desmarque-a.

Além disso, você deve herdar da subclasseException mais específica que está intimamente relacionada àquela que você deseja lançar. Se essa classe não existir, escolhaException como o pai.

Q11. Quais são algumas vantagens das exceções?

As técnicas tradicionais de detecção e tratamento de erros geralmente levam a códigos de espaguete difíceis de manter e difíceis de ler. No entanto, as exceções nos permitem separar a lógica principal do nosso aplicativo dos detalhes do que fazer quando algo inesperado acontece.

Além disso, como a JVM pesquisa para trás na pilha de chamadas para encontrar quaisquer métodos interessados ​​em lidar com uma exceção específica; ganhamos a capacidade de propagar um erro na pilha de chamadas sem escrever código adicional.

Além disso, como todas as exceções lançadas em um programa são objetos, elas podem ser agrupadas ou categorizadas com base em sua hierarquia de classes. Isso nos permite capturar as exceções de um grupo em um único manipulador de exceção, especificando a superclasse da exceção no blococatch.

Q12. Você pode lançar alguma exceção dentro do corpo de uma expressão Lambda?

Ao usar uma interface funcional padrão já fornecida por Java, você só pode lançar exceções não verificadas, porque as interfaces funcionais padrão não possuem uma cláusula "throws" nas assinaturas de método:

List integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {
    if (i == 0) {
        throw new IllegalArgumentException("Zero not allowed");
    }
    System.out.println(Math.PI / i);
});

No entanto, se você estiver usando uma interface funcional personalizada, é possível lançar exceções verificadas:

@FunctionalInterface
public static interface CheckedFunction {
    void apply(T t) throws Exception;
}
public void processTasks(
  List taks, CheckedFunction checkedFunction) {
    for (Task task : taks) {
        try {
            checkedFunction.apply(task);
        } catch (Exception e) {
            // ...
        }
    }
}

processTasks(taskList, t -> {
    // ...
    throw new Exception("Something happened");
});

Q13. Quais são as regras que precisamos seguir ao anular um método que gera uma exceção?

Várias regras determinam como as exceções devem ser declaradas no contexto da herança.

Quando o método da classe pai não lança nenhuma exceção, o método da classe filha não pode lançar nenhuma exceção verificada, mas pode lançar qualquer não verificada.

Aqui está um exemplo de código para demonstrar isso:

class Parent {
    void doSomething() {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IllegalArgumentException {
        // ...
    }
}

O próximo exemplo falhará na compilação, pois o método de substituição lança uma exceção verificada não declarada no método de substituição:

class Parent {
    void doSomething() {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IOException {
        // Compilation error
    }
}

Quando o método da classe pai lança uma ou mais exceções verificadas, o método da classe filho pode lançar qualquer exceção desmarcada; todas, nenhuma ou um subconjunto das exceções verificadas declaradas e até um número maior delas, desde que tenham o mesmo escopo ou menor.

Aqui está um exemplo de código que segue a regra anterior com sucesso:

class Parent {
    void doSomething() throws IOException, ParseException {
        // ...
    }

    void doSomethingElse() throws IOException {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IOException {
        // ...
    }

    void doSomethingElse() throws FileNotFoundException, EOFException {
        // ...
    }
}

Observe que os dois métodos respeitam a regra. O primeiro lança menos exceções do que o método substituído e o segundo, embora lance mais; eles são mais estreitos em escopo.

No entanto, se tentarmos lançar uma exceção verificada que o método da classe pai não declara ou lançarmos uma com um escopo mais amplo; obteremos um erro de compilação:

class Parent {
    void doSomething() throws FileNotFoundException {
        // ...
    }
}

class Child extends Parent {
    void doSomething() throws IOException {
        // Compilation error
    }
}

Quando o método da classe pai possui uma cláusula throws com uma exceção não marcada, o método da classe filho pode lançar nenhum ou qualquer número de exceções não verificadas, mesmo que não estejam relacionadas.

Aqui está um exemplo que respeita a regra:

class Parent {
    void doSomething() throws IllegalArgumentException {
        // ...
    }
}

class Child extends Parent {
    void doSomething()
      throws ArithmeticException, BufferOverflowException {
        // ...
    }
}

Q14. O código a seguir será compilado?

void doSomething() {
    // ...
    throw new RuntimeException(new Exception("Chained Exception"));
}

Yes. Ao encadear exceções, o compilador só se preocupa com o primeiro na cadeia e, como detecta uma exceção não verificada, não precisamos adicionar uma cláusula throws.

Q15. Existe alguma maneira de lançar uma exceção marcada de um método que não tem uma cláusula de lançamento?

Yes. Podemos tirar proveito do apagamento de tipo realizado pelo compilador e fazê-lo pensar que estamos lançando uma exceção não verificada, quando, de fato; estamos lançando uma exceção verificada:

public  T sneakyThrow(Throwable ex) throws T {
    throw (T) ex;
}

public void methodWithoutThrows() {
    this.sneakyThrow(new Exception("Checked Exception"));
}

3. Conclusão

Neste artigo, exploramos algumas das perguntas que provavelmente aparecerão em entrevistas técnicas para desenvolvedores Java, sobre exceções. Esta não é uma lista exaustiva e deve ser tratada apenas como o início de mais pesquisas.

Nós, por exemplo, desejamos sucesso em todas as próximas entrevistas.