Чтение 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 extends X> 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 extends X> 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 для этого 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 extends Throwable>
как тип параметра. Но тогда вы потеряете переменную типа для использования в предложении throws
.
После редактирования:
Почему не просто
throws Throwable
, так как остальные будут стерты стиранием типа?
Вы хотите, чтобы throws
использовали тот же тип Exception
который Supplier
поставляет.
catch(Throwable t)
ли этот метод быть пойманным вcatch(Throwable t)
?
Нет / зависит. Если вы привязываете проверенное исключение к X
, вам нужно будет каким-то образом обработать его. В противном случае catch
не требуется. Но что-то будет / должно справиться с этим в конце концов, даже если это сам JVM.