Специфическая особенность вывода типа исключения в Java 8

При написании кода для другого ответа на этом сайте я столкнулся с этой особенностью:

static void testSneaky() {
  final Exception e = new Exception();
  sneakyThrow(e);    //no problems here
  nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception
}

@SuppressWarnings("unchecked")
static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

static <T extends Throwable> void nonSneakyThrow(T t) throws T {
  throw t;
}

Во-первых, я довольно смущен, почему вызов sneakyThrow подходит компилятору. Какой возможный тип он сделал для T, когда нет упоминания о неограниченном типе исключений?

Во-вторых, признавая, что это работает, почему компилятор жалуется на вызов nonSneakyThrow? Они кажутся очень похожими.

Ответ 1

Значение T sneakyThrow определяется как RuntimeException. Это можно выполнить из спецификации langauge по типу вывода (http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)

Во-первых, есть примечание в разделе 18.1.3:

Оценка формы throws α является чисто информативной: она позволяет разрешить оптимизацию экземпляра α, чтобы, по возможности, это не проверенный тип исключения.

Это ничего не влияет, но указывает на раздел разрешения (18.4), в котором содержится дополнительная информация о предполагаемых типах исключений со специальным случаем:

... В противном случае, если связанное множество содержит throws αi, а собственные верхние границы αi не более, Exception, Throwable и Object, то Ti = RuntimeException.

Этот случай относится к sneakyThrow - единственная верхняя граница - Throwable, поэтому T определяется как RuntimeException согласно спецификации, поэтому она компилируется. Тело метода не имеет значения - неконтролируемое исполнение выполняется во время выполнения, потому что оно на самом деле не происходит, оставляя метод, который может победить систему исключений, зависящую от времени компиляции.

nonSneakyThrow не компилируется, так как этот метод T получил нижнюю границу Exception (т.е. T должен быть супертипом Exception или Exception), который является проверенным исключением, из-за типа, с которым он вызывается, так что T определяется как Exception.

Ответ 2

Если вывод типа создает единственную верхнюю границу для переменной типа, обычно в качестве решения выбирается верхняя граница. Например, если T<<Number, решение равно T=Number. Хотя Integer, Float и т.д. Также могут удовлетворять ограничению, нет веских оснований выбирать их по Number.

Это было также в случае throws T в java 5-7: T<<Throwable => T=Throwable. (У Sneaky throw solutions все были явные аргументы типа <RuntimeException>, иначе <Throwable> выведено.)

В java8 с введением лямбда это становится проблематичным. Рассмотрим этот случай

interface Action<T extends Throwable>
{
    void doIt() throws T;
}

<T extends Throwable> void invoke(Action<T> action) throws T
{
    action.doIt(); // throws T
}    

Если мы будем ссылаться на пустую лямбда, что бы было T выведено как?

    invoke( ()->{} ); 

Единственное ограничение на T - верхняя граница Throwable. На ранней стадии java8 было бы выведено T=Throwable. См. Этот отчет, который я подал.

Но это довольно глупо, вывести Throwable, исключенное исключение из пустого блока. В докладе было предложено решение (которое, по-видимому, было принято JLS) -

If E has not been inferred from previous steps, and E is in the throw clause, 
and E has an upper constraint E<<X,
    if X:>RuntimeException, infer E=RuntimeException
    otherwise, infer E=X. (X is an Error or a checked exception)

то есть. если верхняя граница Exception или Throwable, выберите RuntimeException в качестве решения. В этом случае есть веская причина выбрать конкретный подтип верхней границы.

Ответ 3

С sneakyThrow тип T представляет собой ограниченную типичную переменную типа без определенного типа (поскольку там нет типа, из которого может возникнуть тип).

С nonSneakyThrow тип T является тем же типом, что и аргумент, поэтому в вашем примере T of nonSneakyThrow(e); - Exception. Поскольку testSneaky() не объявляет сброшенный Exception, отображается ошибка.

Обратите внимание, что это известная интерференция Generics с проверенными исключениями.