Статьи

Злой Java-трюк, заставляющий JVM забыть проверить исключения

Я долгое время критиковал механизм проверенных компилятором исключений в Java. Любите ли вы их или ненавидите тогда, одно несомненно: есть ситуации, когда вы не хотите иметь с ними дело. Решение в Java — new RuntimeException(e) проверенное исключение в new RuntimeException(e) но это дает длинные трассировки стека без добавления полезной информации. Иногда мы просто хотим сказать компилятору успокоиться.

Как выясняется, это возможно из-за неосторожного злоупотребления ошибкой в ​​стирании типов Java-дженериков. Увидеть это в действии полезно для понимания внутренней работы Java. Поехали!

Вот что мы хотим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public static void main(String[] args) {
      businessLogic();
  }
 
  private static void businessLogic() {
      List<String> configuration = readConfigurationFile();
      System.out.println(configuration.get(0));
  }
 
  private static List<String> readConfigurationFile() {
      try {
          return Files.readAllLines(Paths.get("non", "existing", "file"));
      } catch (IOException e) {
          throw softenException(e);
      }
  }

Обратите внимание, что businessLogic() ни перехватывает IOException ни объявляет, что throws IOException . Вместо этого метод softenException() удаляет проверенную исключительность. Когда мы запускаем его, мы получаем следующую трассировку стека:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Exception in thread "main" java.nio.file.NoSuchFileException: non\existing\file
    at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
    at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230)
    at java.nio.file.Files.newByteChannel(Files.java:361)
    at java.nio.file.Files.newByteChannel(Files.java:407)
    at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384)
    at java.nio.file.Files.newInputStream(Files.java:152)
    at java.nio.file.Files.newBufferedReader(Files.java:2784)
    at java.nio.file.Files.readAllLines(Files.java:3202)
    at java.nio.file.Files.readAllLines(Files.java:3242)
    at insanejava.SoftenExceptionsDemo.readConfigurationFile(SoftenExceptionsDemo.java:21)
    at insanejava.SoftenExceptionsDemo.businessLogic(SoftenExceptionsDemo.java:15)
    at insanejava.SoftenExceptionsDemo.main(SoftenExceptionsDemo.java:11)

Ха! Исключение, которое NoSuchFileException методе main, представляет собой NoSuchFileException , который является подклассом IOException — проверенное исключение! Как это может быть? Почему ни один из методов в программе не должен объявлять throws IOException ?

Вот хитрость:

1
2
3
4
5
6
7
private static RuntimeException softenException(Exception e) {
      return checkednessRemover(e);
  }
 
  private static <T extends Exception> T checkednessRemover(Exception e) throws T {
      throw (T) e;
  }

Метод checkednessRemover использует трюк, который раскрывает несколько вещей о внутренней работе Java. Во-первых, аргумент универсального типа T связан с RuntimeException для выполнения контракта softenException . Это означает, что выражение throws T становится throws RuntimeException , которое компилятор интерпретирует, как если бы не было выброшенных исключений.

Но утверждение throw (T)e; Теоретически следует оценить, чтобы throw (RuntimeException)e; , Поскольку e является NoSuchFileException , можно ожидать, что этот оператор приведет к ClassCastException . Но как работают дженерики в Java, информация о типе удаляется компилятором. Поэтому вместо этого байт-код читается как throw (Exception)e; это нормально.

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

Я бы порекомендовал использовать этот трюк в рабочем коде? Я не знаю. Это довольно странно и, возможно, не очень полезно, но я использую его сам, когда чувствую себя злым. Надеюсь, что если вы узнаете что-то еще, вы получили некоторое представление о внутренней работе Java.

Отказ от ответственности : (1) Я читал об этом трюке в другом месте, но я не могу найти источник больше. Я думал, что это была отличная новостная рассылка Heinz Kabutz для Java-специалиста , но я не могу найти источник. (2) Это также реализовано в Project Lombok как @SneakyThrows . Если вы используете Lombok, вы ни при каких обстоятельствах не должны повторно использовать трюк из этого блога. @SneakyThrows этого используйте @SneakyThrows .

Опубликовано на Java Code Geeks с разрешения Йоханнеса Бродвола, партнера нашей программы JCG. См. Оригинальную статью здесь: Злая уловка Java, чтобы заставить JVM забыть проверить исключения

Мнения, высказанные участниками Java Code Geeks, являются их собственными.