throws x расширяет подпись метода исключения

Чтение JavaDoc Optional , я столкнулся с необычной сигнатурой метода; Я никогда не видел в своей жизни:

 public  T orElseThrow(Supplier exceptionSupplier) throws X extends Throwable 

На первый взгляд, я задавался вопросом, возможно ли вообще исключение , так как вы не можете этого сделать ( здесь и здесь ). С другой стороны, это начинает иметь смысл, так как здесь просто связывать Supplier … но сам поставщик точно знает, какой тип он должен быть, до дженериков.

Но вторая строка поразила меня:

  • throws X – полный общий тип исключения.

А потом:

  • X extends Throwable , что в мире это означает?
    • X уже связан в сигнатуре метода.
  • Будет ли это каким-либо образом решить универсальное ограничение исключения?
  • Почему не просто throws Throwable , так как остальные будут стерты стиранием типа?

И один, а не непосредственно связанный с ним вопрос:

  • catch(Throwable t) ли этот метод быть пойманным как catch(Throwable t) или по предоставленному Supplier типу; поскольку он не может быть проверен во время выполнения?

Относитесь к нему, как и любой другой общий код, который вы прочитали.

Вот формальная подпись из того, что я вижу в исходном коде Java 8 :

 public  T orElseThrow(Supplier exceptionSupplier) throws X 
  • X имеет верхнюю границу Throwable . Это будет важно позже.
  • Мы возвращаем тип T привязанный к Optional T
  • Мы ожидаем, что Supplier имеющий верхнюю границу шаблона X
  • Мы бросаем X (что верно, так как X имеет верхнюю границу Throwable ). Это указано в JLS 8.4.6 ; пока X рассматривается как подтип Throwable , его декларация действительна и легальна здесь.

Существует ошибка, связанная с тем, что Джавадок вводит в заблуждение. В этом случае лучше всего доверять исходному коду, а не документации, пока ошибка не будет объявлена ​​фиксированной.

Что касается того, почему мы используем throws X вместо throws Throwable : X гарантированно привязан к Throwable в самых верхних границах. Если вы хотите более конкретное Throwable (время выполнения, проверка или Error ), то просто бросать Throwable не даст вам такой гибкости.

К вашему последнему вопросу:

Должен ли этот метод быть пойманным в уловке (Throwable t)?

Что-то в цепочке должно обрабатывать исключение, будь то блок try...catch или сам JVM. В идеале хотелось бы создать Supplier связанного с исключением, которое наилучшим образом передало их потребности. Вам не нужно (и, вероятно, не должно) создавать catch(Throwable t) для этого случая; если ваш Supplier привязан к конкретному исключению, которое вам нужно обработать, тогда лучше использовать его как ваш catch позже в цепочке.

Подпись метода Javadoc отличается от исходного кода. Согласно источнику b132 :

 public  T orElseThrow(Supplier exceptionSupplier) throws X { // Added by me: See? No X extends Throwable if (value != null) { return value; } else { throw exceptionSupplier.get(); } } 

Подтверждено: это проблема с поколением Javadoc. В качестве теста я создал новый проект и создал Javadoc для следующего classа:

 package com.stackoverflow.test; public class TestJavadoc { public static  void doSomething() throws X { } } 

Это результат Javadoc:

Скриншот Javadoc

Двойной подтвержден: есть открытый баг о Javadoc для этого classа

Java не может поймать общие типы исключений из-за стирания

  catch(FooException e) --analogy-> o instanceof List 

catch и instanceof зависят от информации типа времени выполнения; стирание руин это.

Тем не менее, это слишком жестко, чтобы запретить общее объявление типов исключений, таких как FooException . Java может это позволить; просто требуется, чтобы в уловке разрешались только сырые типы и подстановочный знак

  catch(FooException e) --analogy-> o instanceof List catch(FooException e) o instanceof List 

Можно привести аргумент, что тип исключения в основном интересуется предложениями catch; если мы не сможем поймать общие типы исключений, нет смысла иметь их в первую очередь. ОК.


Теперь мы не можем этого сделать

  catch(X e) // X is a type variable 

то почему Java позволяет X быть брошенным в первую очередь?

  ... foo() throws X 

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

Ну, эта функция весьма полезна, хотя – по крайней мере, концептуально. Мы пишем общие методы, чтобы мы могли писать код шаблона для неизвестных типов ввода и возврата; та же логика для типов бросков! Например

   void logAndThrow(X ex) throws X ... throw ex; ... (IOException e){ logAndThrow(e); // throws IOException 

Чтобы абстрагироваться от блока кода, мы должны иметь возможность генерировать общий доступ к прозрачности исключений. Другой пример, говорят, что мы устали многократно писать этот тип плитки

  lock.lock(); try{ CODE }finally{ lock.unlock(); } 

и мы хотим, чтобы для этого был метод обертки, взяв lambda для КОДА

  Util.withLock(lock, ()->{ CODE }); 

Проблема в том, что CODE может генерировать любой тип исключения; исходный шаблон бросает все, что бросает КОД; и мы хотим, writeLock() выражение writeLock() выполняло то же самое, т. е. прозрачность исключений. Решение –

  interface Code { void run() throws X; }  void withLock(Lock lock, Code code) throws X ... code.run(); // throws X ... withLock(lock, ()->{ throws new FooException(); }); // throws FooException 

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

Одним из серьезных недостатков подхода throws X является то, что он не работает с 2 или более типами исключений.


Хорошо, это действительно приятно. Но мы не видим такого использования в новых API JDK. Никакие функциональные типы любого параметра метода не могут вызывать любое проверенное исключение. Если вы выполняете Stream.map(x->{ BODY }) , BODY не может выбрасывать какие-либо проверенные исключения. Они действительно ненавидят проверенные исключения с lambdaми.

Другим примером является CompletableFuture , где исключение является центральной частью всей концепции; Тем не менее, исключенные исключения не допускаются ни в lambda-телах.

Если вы мечтатель, вы можете поверить, что в какой-то будущей версии Java будет изобретен элегантный механизм прозрачности исключений для лямбды, ничего подобного уродливому throws X хак; поэтому нам не нужно загрязнять наши API с помощью s. Да, сон, человек.


Итак, сколько же вариантов throws X используется в любом случае?

На протяжении всего источника JDK единственный публичный API, который делает это, является Optional.orElseThrow() (и его кузены OptionalInt/Long/Double ). Вот и все.

(Существует недостаток с orElseGet/orElseThrow – решение ветвления не может быть выполнено в lambda-теле. Мы могли бы вместо этого разработать более общий метод

  T orElse( lambda ) throws X optional.orElse( ()->{ return x; } ); optional.orElse( ()->{ throw ex; } ); optional.orElse( ()->{ if(..)return x; else throw ex; } ); 

Помимо orElseThrow , единственным другим методом в JDK, который throws X является непубличный ForkJoinTask.uncheckedThrow() . Он используется для «скрытого броска», то есть код может вызывать проверенное исключение, без компилятора и времени выполнения, зная об этом –

  void foo() // no checked exception declared in throws { throw sneakyThrow( new IOExcepiton() ); } 

здесь IOException из foo() во время выполнения; однако компилятор и среда выполнения не смогли его предотвратить. В основном виноват Erasure.

 public static RuntimeException sneakyThrow(Throwable t) { throw Util.sneakyThrow0(t); } @SuppressWarnings("unchecked") private static  T sneakyThrow0(Throwable t) throws T { throw (T)t; } 

Существует различие между типичными типами и общими переменными / параметрами.

Тип общего типа – это тип, который объявляет общий параметр. Например

 class Generic {} 

То, что не допускается, – это общий тип (например, выше), который также расширяет Throwable .

 class Generic  extends Throwable {} // nono 

Это выражается в спецификации языка Java, здесь

Это ошибка времени компиляции, если общий class является прямым или косвенным подclassом Throwable (§11.1.1).

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

Эта

X extends Throwable

означает, что любой тип привязан (не имеет границ) к X во время компиляции должен быть подтипом Throwable .

X уже связан в сигнатуре метода.

X не связан. Метод объявляет его границы.

Будет ли это каким-либо образом решить универсальное ограничение исключения?

Нет.

Почему бы не просто extends Throwable , так как остальные будут стерты стиранием типа?

Дженерики – это функция времени компиляции. Erasure происходит после компиляции. Я считаю, что они могли просто использовать

 Supplier 

как тип параметра. Но тогда вы потеряете переменную типа для использования в предложении throws .

После редактирования:

Почему не просто throws Throwable , так как остальные будут стерты стиранием типа?

Вы хотите, чтобы throws использовали тот же тип Exception который Supplier поставляет.

catch(Throwable t) ли этот метод быть пойманным в catch(Throwable t) ?

Нет / зависит. Если вы привязываете проверенное исключение к X , вам нужно будет каким-то образом обработать его. В противном случае catch не требуется. Но что-то будет / должно справиться с этим в конце концов, даже если это сам JVM.