Osobliwa cecha wnioskowania typu wyjątku w Javie 8

Podczas pisania kodu do innej odpowiedzi na tej stronie natknąłem się na tę osobliwość:

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;
}

Po pierwsze, jestem dość zdezorientowany, dlaczego wywołanie sneakyThrow jest OK dla kompilatora. Jaki typ można wywnioskować dla T, gdy nigdzie nie ma wzmianki o niezaznaczonym typie wyjątku?

Po Drugie, przyjmując, że to działa, dlaczego kompilator skarży się na wywołanie nonSneakyThrow? Wydają się bardzo podobne.

Author: Makoto, 2015-07-09

3 answers

T z sneakyThrow przyjmuje się jako RuntimeException. To może być wykonane z langauge spec na typ wnioskowania ( http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html )

Po pierwsze, jest notatka w sekcji 18.1.3:

Wiązanie formularza throws α ma charakter czysto informacyjny: kieruje rozdzielczość w celu optymalizacji instancji α tak, aby, jeśli to możliwe, nie był to sprawdzony typ wyjątku.

To nie ma wpływu na nic, ale wskazuje nam na w sekcji rozdzielczość (18.4), która zawiera więcej informacji na temat wnioskowanych typów wyjątków ze szczególnym przypadkiem:

... W przeciwnym razie, jeśli zbiór bound zawiera throws αi, a właściwe górne granice ai są co najwyżej, Exception, Throwable, i Object, Następnie Ti = RuntimeException.

Ten przypadek dotyczy sneakyThrow - jedyną górną granicą jest Throwable, więc T wnioskuje się jako RuntimeException zgodnie ze specyfikacją, więc kompiluje. Ciało metody jest niematerialne - niezaangażowany odlew udaje się w czasie wykonywania ponieważ tak się nie dzieje, pozostawiając metodę, która może pokonać system wyjątków sprawdzonych w czasie kompilacji.

nonSneakyThrow nie kompiluje się, ponieważ T ma dolną granicę Exception (tzn. T musi być supertyp Exception, lub sama Exception), co jest wyjątkiem sprawdzonym, ze względu na typ, z którym jest wywoływana, tak że T jest wnioskowana jako Exception.

 66
Author: thecoop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-09 12:32:18

Jeśli wnioskowanie typu tworzy pojedynczą górną granicę dla zmiennej typu, zazwyczaj górna granica jest wybierana jako rozwiązanie. Na przykład, jeśli T<<Number, rozwiązaniem jest T=Number. Chociaż Integer, Float itd. może również zaspokoić ograniczenia, nie ma dobrego powodu, aby wybrać je zamiast Number.

Tak było również w przypadku throws T w Javie 5-7: T<<Throwable => T=Throwable. (Sneaky throw solutions all had explicit <RuntimeException> type arguments, otherwise <Throwable> is conferred.)

W java8, z wprowadzeniem lambda, to staje się problematyczne. Rozważmy ten przypadek

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

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

Jeśli wywołamy pustą lambdę, to co T można wywnioskować jako?

    invoke( ()->{} ); 

Jedynym ograniczeniem na T jest górna granica Throwable. We wcześniejszym etapie java8, T=Throwable można by wywnioskować. Zobacz ten raport , który złożyłem.

Ale to jest dość głupie, wywnioskować Throwable, sprawdzony wyjątek, z pustego bloku. W sprawozdaniu zaproponowano rozwiązanie (które najwyraźniej zostało przyjęte przez 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)

Tj. Jeśli górna granica wynosi Exception lub Throwable, wybierz RuntimeException jako rozwiązanie. W tym przypadku istnieje jest dobry powód, aby wybrać konkretny Podtyp górnej granicy.

 17
Author: ZhongYu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-10 01:48:50

Z sneakyThrow, typ T jest ograniczoną zmienną typu ogólnego bez określonego typu(ponieważ nie ma skąd Typ może pochodzić).

Z nonSneakyThrow, typ T jest tym samym typem co argument, więc w twoim przykładzie T z nonSneakyThrow(e); jest Exception. Ponieważ testSneaky() nie deklaruje rzuconego Exception, wyświetlany jest błąd.

Zauważ, że jest to znana interferencja leków generycznych z zaznaczonymi wyjątkami.

 1
Author: llogiq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-09 11:54:34