Ostatnio prowadzę sporą ilość rekrutacji do projektu, na którym jestem. Dzięki temu mam możliwość poznać ciekawych ludzi, od których jestem w stanie się czegoś nauczyć. Podczas rozmowy rekrutacyjnej proszę kandydatów o zrobienie code review kodu, który “napisałem”. W jednej z linijek tego kodu znajduje się tytułowa adnotacja Lomboka @SneakyThrows
. To czy jej wykorzystanie jest antypatternem czy też nie, nie mi to osądzać. Natomiast na rozmowie rekrutacyjnej jeden z kandydatów powiedział, że wykorzystanie adnotacji @SneakyThrows
jest w 100% złe, ponieważ miesza ona w strukturze bytecode aplikacji. Wydaje mi się, że wszystkie adnotacje Lomboka tak robią… Niemniej zaciekawiło mnie jak to faktycznie wygląda pod maską dla @SneakyThrows
. I oto co znalazłem.
Wszystko jest fajnie opisane na blogu Dev Genius. Okazuje się, że twórcy Lomboka wykorzystali po prostu trick oszukujący kompilator. W jaki sposób? Sprawdźmy.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
}
static void testSneaky() {
final Exception e = new Exception();
sneakyThrow(e); // No Errors
nonSneakyThrow(e); // Error: Unhandled exception: java.lang.Exception
}
Skopiuj to sobie do IDE i przetrzyj oczy ze zdumienia. Kompilator z jakiegoś powodu zaczyna wierzyć, że ma do czynienia z unchecked exception zamiast z checked exception. Magia? Nie, to po prostu specyfika języka Java. Zostało to w mniej lub bardziej zrozumiały sposób opisane w dokumentacji. Wszystko oczywiście ma związek z wielkią zmianą, która zaszła w Javie. Dokładniej mówiąc - dotyczącą typów generycznych i ich wnioskowania. W powyższej dokumentacji możemy przeczytać:
Otherwise, if the bound set contains throws αi, and the proper upper bounds of αi are, at most, Exception, Throwable, and Object, then Ti = RuntimeException.
Czyli kompilator zakłada, że jeśli dla danej klasy w hierarchii dziedziczenia są przynajmniej klasy Object
, Throwable
i Exception
to jest to całkiem prawdopodobne, że typem tej klasy jest RuntimeException
. I tak to właśnie wygląda w przypadku metody sneakyThrow
. Natomiast w nonSneakyThrow
nie mamy czego wnioskować, bo przekazany do niej obiekt jest przypisany do parametru typu T
. Wtedy komplilator zakłada, że on już na pewno będzie po prostu dalej tym samym typem co wcześniej np. checked exception. Oto cała sztuczka! Czy wiedziałeś/wiedziałaś o tym?
Powyższy kod możesz znaleźć na GitHub projektu Lombok.