Pisząc kod dla 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 sneakyThrow
wywołanie kompilatora jest OK. Jaki możliwy typ to wywnioskowało, T
kiedy nigdzie nie ma wzmianki o niezaznaczonym typie wyjątku?
Po drugie, przyjmując, że to działa, dlaczego kompilator narzeka na nonSneakyThrow
wywołanie? Wydają się do siebie bardzo podobni.
źródło
sneakyThrow
wezwanie. Specjalne przepisy dotyczące wnioskowania zthrows T
formularzy nie istniały w specyfikacji Java 7.nonSneakyThrow
,T
musi byćException
, a nie „nadtypem”Exception
, ponieważ jest to dokładnie typ argumentu zadeklarowanego w czasie kompilacji w miejscu wywołania.Exception
i górną granicęThrowable
, a więc najmniejszą górną granicą, która jest wynikiem wywnioskowanego typu, jestException
.Exception
fraza „lub sama” może być pomocna dla czytelnika, ale ogólnie należy zauważyć, że specyfikacja zawsze używa terminów „podtyp” i „nadtyp” w znaczeniu „obejmujący siebie”…Jeśli wnioskowanie o typie tworzy pojedynczą górną granicę dla zmiennej typu, zwykle jako rozwiązanie wybierana jest górna granica. Na przykład, jeśli
T<<Number
rozwiązaniem jestT=Number
. MimoInteger
,Float
itd. Mogą również spełniać ograniczenia, nie ma dobry powód, aby wybrać jeNumber
.To było również w przypadku
throws T
w java 5-7T<<Throwable => T=Throwable
. (Wszystkie rozwiązania podstępnego rzutowania miały jawne<RuntimeException>
argumenty typu, w przeciwnym razie<Throwable>
są wywnioskowane).W java8, wraz z wprowadzeniem lambdy, staje się to problematyczne. Rozważ 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 z pustą lambdą, co
T
zostanie wywnioskowane jako?Jedynym ograniczeniem
T
jest górna granicaThrowable
. Na wcześniejszym etapie java8T=Throwable
można by wywnioskować. Zobacz ten raport, który złożyłem.Ale to jest dość głupie, wnioskując
Throwable
, sprawdzony wyjątek z pustego bloku. W raporcie zaproponowano rozwiązanie (najwyraźniej 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órną granicą jest
Exception
lubThrowable
, wybierzRuntimeException
jako rozwiązanie. W tym przypadku nie jest to dobry powód, aby wybrać konkretny podtyp górna granica.źródło
X:>RuntimeException
w ostatnim przykładowym fragmencie?W
sneakyThrow
przypadku typT
jest ograniczoną zmienną typu ogólnego bez określonego typu (ponieważ nie ma miejsca, z którego może pochodzić typ).W
nonSneakyThrow
przypadku typT
jest tego samego typu co argument, dlatego w naszym przykładzieT
ofnonSneakyThrow(e);
toException
. PonieważtestSneaky()
nie deklaruje rzuceniaException
, wyświetlany jest błąd.Zauważ, że jest to znana interferencja Generics z zaznaczonymi wyjątkami.
źródło
sneakyThrow
tak naprawdę nie jest to wywnioskowane do żadnego konkretnego typu, a „rzutowanie” dotyczy takiego niezdefiniowanego typu? Zastanawiam się, co się z tym właściwie dzieje.