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

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

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
                                throws X extends Throwable

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

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

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

И затем:

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

И один вопрос, не имеющий прямого отношения:

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

Ответ 1

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

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

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

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

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

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

Будет ли требоваться, чтобы этот метод попадал в предложение catch (Throwable t)?

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

Ответ 2

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

public <X extends Throwable> 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 для следующего класса:

package com.stackoverflow.test;

public class TestJavadoc {
    public static <X extends Throwable> void doSomething() throws X {

    }
}

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

Javadoc Screenshot

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

Ответ 3

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

    catch(FooException<Bar> e)        --analogy->        o instanceof List<String>

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

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

разрешены только сырые типы и подстановочный знак,
    catch(FooException e)              --analogy->       o instanceof List
    catch(FooException<?> e)                             o instanceof List<?>

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


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

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

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

    ... foo() throws X

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

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

    <X extends Exception> void logAndThrow(X ex) throws X
        ...
        throw ex;

    ...
    (IOException e){   
        logAndThrow(e);   // throws IOException

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

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

и нам нужен метод обертки для этого, взяв лямбду для CODE

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

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

    interface Code<X extends Throwable>
    {
        void run() throws X;
    }



    <X extends Throwable> void withLock(Lock lock, Code<X> code) throws X
        ...
        code.run();  // throws X

    ...
    withLock(lock, ()->{ throws new FooException(); });  // throws FooException

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

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


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

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

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


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

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

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

    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.<RuntimeException>sneakyThrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T
{
    throw (T)t;
}

Ответ 4

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

Общий тип - это тип, объявляющий общий параметр. Например

class Generic<T> {}

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

class Generic <T> extends Throwable {} // nono

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

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

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

Это

X extends Throwable

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

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

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

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

Нет.

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

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

Supplier<? extends Throwable>

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

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

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

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

Требуется ли, чтобы этот метод попадал в предложение catch(Throwable t)?

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