Перейти к основному содержимому

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

· 3 мин. чтения

1. Обзор

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

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

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

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

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

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

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

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

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

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

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

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

@Test
public void throwSneakyIOException_IOExceptionShouldBeThrown() {
assertThatThrownBy(() -> throwSneakyIOException())
.isInstanceOf(IOException.class)
.hasMessage("sneaky")
.hasStackTraceContaining("SneakyThrowsExamples.throwSneakyIOException");
}

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

4. Использование аннотаций Ломбока

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

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

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

@SneakyThrows
public static void throwSneakyIOExceptionUsingLombok() {
throw new IOException("lombok sneaky");
}

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

Теперь давайте вызовем throwSneakyIOExceptionUsingLombok и ожидаем, что Lombok выдаст исключение IOException:

@Test
public void throwSneakyIOExceptionUsingLombok_IOExceptionShouldBeThrown() {
assertThatThrownBy(() -> throwSneakyIOExceptionUsingLombok())
.isInstanceOf(IOException.class)
.hasMessage("lombok sneaky")
.hasStackTraceContaining("SneakyThrowsExamples.throwSneakyIOExceptionUsingLombok");
}

5. Вывод

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

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