«1. Обзор

В Java концепция скрытого броска позволяет нам генерировать любое проверенное исключение, не определяя его явно в сигнатуре метода. Это позволяет опустить объявление throws, эффективно имитируя характеристики исключения времени выполнения.

В этой статье мы увидим, как это делается на практике, рассмотрев несколько примеров кода.

2. О Sneaky Throws

Проверяемые исключения являются частью Java, а не JVM. В байт-коде мы можем кидать любое исключение откуда угодно, без ограничений.

В Java 8 появилось новое правило вывода типов, которое гласит, что throws T выводится как RuntimeException всякий раз, когда это разрешено. Это дает возможность реализовать скрытые броски без вспомогательного метода.

Проблема скрытых бросков заключается в том, что вы, вероятно, захотите перехватывать исключения в конце концов, но компилятор Java не позволяет вам перехватывать скрытно генерируемые проверенные исключения, используя обработчик исключений для их конкретного типа исключения.

3. Подлые броски в действии

Как мы уже упоминали, компилятор и среда выполнения Jave могут видеть разные вещи: это позволяет непроверенному исключению распространяться. Среда выполнения Java не видит никакого типа в бросках, так как все броски одинаковы: простой бросок e.

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

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

Этот быстрый тест демонстрирует сценарий:

Можно сгенерировать проверенное исключение, используя манипуляции с байт-кодом или Thread.stop(Throwable), но это запутанно и не рекомендуется.

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

4. Использование аннотаций Lombok

Аннотация @SneakyThrows от Lombok позволяет вам генерировать проверенные исключения без использования объявления throws. Это удобно, когда вам нужно вызвать исключение из метода в очень ограниченных интерфейсах, таких как Runnable.

Допустим, мы выбрасываем исключение из Runnable; он будет передан только обработчику необработанных исключений потока.

Этот код выдает экземпляр Exception, поэтому вам не нужно оборачивать его в RuntimeException:

Недостаток этого кода в том, что вы не можете перехватить проверенное исключение, которое не объявлено; поэтому он не будет компилироваться.

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

Вот правильная форма для выдачи незаметного исключения:

А вот проверка этого поведения:

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

5. Заключение

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

Как мы видели в этой статье, компилятор Java можно обмануть, чтобы обрабатывать проверенные исключения как непроверенные.

Как всегда, код доступен на GitHub.

«