Своєрідна особливість умовиводу щодо винятків у Java 8


85

Під час написання коду для іншої відповіді на цьому сайті я натрапив на цю особливість:

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дзвінок? Вони здаються дуже схожими.

Відповіді:


66

Т sneakyThrowвиводиться буде RuntimeException. Цього можна зрозуміти із специфікації мови щодо виведення типу ( http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html )

По-перше, у розділі 18.1.3 є примітка:

Обмеження форми throws αє суто інформаційним: воно спрямовує роздільну здатність на оптимізацію екземпляра α, щоб, якщо це можливо, це не перевірений тип винятку.

Це ні на що не впливає, але вказує нам на розділ "Роздільна здатність" (18.4), який містить більше інформації про виведені типи винятків із особливим випадком:

... В іншому випадку, якщо пов'язаний набір throws αi, і відповідні верхні межі & alpha ; i є, в кращому випадку , Exception, Throwableі Object, то Ti = RuntimeException.

Цей випадок стосується sneakyThrow- єдиною верхньою межею є Throwable, тому Tвипливає, що вона RuntimeExceptionвідповідає специфікації, тому вона компілюється. Основна частина методу несуттєва - неперевірений привід досягає успіху під час виконання, оскільки насправді цього не відбувається, залишаючи метод, який може перемогти перевірену систему винятків під час компіляції.

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


1
@Maksym Ви повинні мати на увазі sneakyThrowдзвінок. Спеціальні правила щодо виведення throws Tформ не існували в специфікації Java 7.
Марко Топольник

1
Невеликий штрих: In nonSneakyThrow, Tповинен бути Exception, а не "надтип" Exception, оскільки це саме тип аргументу, оголошений під час компіляції на сайті виклику.
llogiq

1
@llogiq Якщо я правильно прочитав специфікацію, вона має нижню межу Exceptionі верхню межу Throwable, отже, найменшою верхньою межею, що є результатом виведеного типу, є Exception.
thecoop

1
@llogiq Зверніть увагу, що тип аргументу встановлює лише нижчий прив’язаний тип, оскільки будь-який супертип аргументу також є прийнятним.
Marko Topolnik

2
Exceptionфраза "або сама" може бути корисною для читача, але загалом слід зазначити, що в специфікації завжди використовуються терміни "підтип" і "надтип" у значенні "включаючи себе" ...
Холгер

17

Якщо умовивід типу створює одну верхню межу для змінної типу, як правило, верхньою межею вибирається рішення. Наприклад, якщо T<<Number, рішення є T=Number. Хоча Integer, і Floatт.д. , також може задовольняти обмеження, немає ніяких підстав , щоб вибрати їх більш Number.

Це було також у разі throws Tв Java 5-7: T<<Throwable => T=Throwable. (Усі підлі рішення для розкидання мали явні <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рішенням. У цьому випадку є вагома причина вибрати конкретний підтип верхньої межі.


Що означає X:>RuntimeExceptionваш останній приклад фрагмента?
марсуф

1

З sneakyThrow, тип Tє обмеженою загальною змінною типу без певного типу (оскільки немає місця, звідки тип міг би походити).

З nonSneakyThrow, тип Tтого ж типу в якості аргументу, таким чином , в вашому прикладі, Tв nonSneakyThrow(e);це Exception. Як testSneaky()не оголошує кинуте Exception, відображається помилка.

Зауважте, що це відоме втручання Generics із перевіреними винятками.


Отже, sneakyThrowнасправді це не випливає з будь-якого конкретного типу, а "закид" - до такого невизначеного типу? Цікаво, що насправді з цим відбувається.
Марко Топольник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.