«Подлый бросок» на Java

«Подлый бросок» на Яве

1. обзор

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

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

2. О скрытых бросках

Checked exceptions are part of Java, not the JVM. В байт-коде мы можем генерировать любое исключение откуда угодно, без ограничений.

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

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

3. Коварные броски в действии

Как мы уже упоминали, компилятор и среда выполнения Jave могут видеть разные вещи:

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, поэтому разрешает распространение непроверенного исключения. Среда выполнения Java не видит никаких типов в бросках, так как все броски одинаковы: простойthrow e.

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

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

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

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

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

Скажем, мы генерируем исключение изRunnable; он будет передан только обработчику необработанного исключенияThread’s.

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

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

Недостатком этого кода является то, что вы не можете поймать проверенное исключение, которое не объявлено; so, it will not compile.

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

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

А вот и тест на такое поведение:

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

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

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

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