"Sneaky Throws" em Java

"Sneaky Throws" em Java

1. Visão geral

Em Java, o conceito sneaky throw nos permite lançar qualquer exceção verificada sem defini-la explicitamente na assinatura do método. Isso permite a omissão da declaraçãothrows, imitando efetivamente as características de uma exceção de tempo de execução.

Neste artigo, veremos como isso é feito na prática, observando alguns exemplos de código.

2. Sobre Sneaky Throws

Checked exceptions are part of Java, not the JVM. No bytecode, podemos lançar qualquer exceção de qualquer lugar, sem restrições.

Java 8 trouxe uma nova regra de inferência de tipo que afirma que athrows T é inferido comoRuntimeException sempre que permitido. Isso permite implementar lances sorrateiros sem o método auxiliar.

Um problema comsneaky throws é que você provavelmente deseja capturar as exceções eventualmente, mas o compilador Java não permite que você capture exceções verificadas lançadas sorrateiramente usando o manipulador de exceções para seu tipo de exceção específico.

3. Jogadas furtivas em ação

Como já mencionamos, o compilador e o Jave Runtime podem ver coisas diferentes:

public static  void sneakyThrow(Throwable e) throws E {
    throw (E) e;
}

private static void throwsSneakyIOException() {
    sneakyThrow(new IOException("sneaky"));
}

The compiler sees the signature with the throws T inferred to a RuntimeException type, portanto, permite que a exceção não verificada se propague. O Java Runtime não vê nenhum tipo nos lances, pois todos os lances são iguais a umthrow e. simples

Este teste rápido demonstra o cenário:

@Test
public void whenCallSneakyMethod_thenThrowSneakyException() {
    try {
        SneakyThrows.throwsSneakyIOException();
    } catch (Exception ex) {
        assertEquals("sneaky", ex.getMessage().toString());
    }
}

É possível lançar uma exceção verificada usando a manipulação de bytecode ouThread.stop(Throwable), mas é confuso e não recomendado.

4. Usando anotações do Lombok

A anotação@SneakyThrows deLombok permite que você lance exceções verificadas sem usar a declaraçãothrows. Isso é útil quando você precisa levantar uma exceção de um método em interfaces muito restritivas comoRunnable.

Digamos que lançamos uma exceção dentro de aRunnable; ele só será passado para o manipulador de exceção não tratadaThread’s.

Este código irá lançar a instânciaException, então não há necessidade de envolvê-lo em umRuntimeException:

public class SneakyRunnable implements Runnable {
    @SneakyThrows(InterruptedException.class)
    public void run() {
        throw new InterruptedException();
    }
}

Uma desvantagem desse código é que você não pode capturar uma exceção verificada que não esteja declarada; so, it will not compile.

Esta é a forma correta de lançar uma exceção sorrateira:

@SneakyThrows
public void run() {
    try {
        throw new InterruptedException();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

E aqui está o teste para este comportamento:

@Test
public void whenCallSneakyRunnableMethod_thenThrowException() {
    try {
        new SneakyRunnable().run();
    } catch (Exception e) {
        assertEquals(InterruptedException.class, e.getStackTrace());
    }
}

5. Conclusão

Como vimos neste artigo, o compilador Java pode ser enganado para tratar as exceções verificadas como desmarcadas.

Como sempre, o código está disponívelover on GitHub.