Przejdź do głównej zawartości

Sneaky throws, czyli checked i unchecked exception w jednym!

Często podczas pisania kodu Java korzystam z wyrażeń lambda wprowadzonych wraz z Java 8. Często prowadzi to też do pewnych nowych problemów i zmusza do szukania rozwiązań. Przyjrzyjmy się jednemu z nich.

Checked exception w wyrażeniu lambda

Często zdarza się, że metoda wywoływana wewnątrz lambdy rzuca wyjątek. Nie ma sprawy gdy wyjątek jest obiektem klasy będącej podklasą RuntimeException. Wtedy po prostu się nim nie przejmujemy. Wyjątek jest przekazywany w górę stosu wywołań. Problem zaczyna się gdy kompilator zmusza nas do obsługi wyjątku. Jak zwykle mamy 2 wyjścia: obsłużyć wyjątek za pomocą bloku try-catch, bądź zadeklarować przekazanie wyjątku dalej za pomocą słowa kluczowego throws. O ile wiemy co zrobić po złapaniu wyjątku to wszystko gra. Co natomiast gdy chcemy wybrać drugą opcję? Większość interfejsów stosowanych jako typy parametrów, często przekazywanych jako lambdy jak np: Function, Consumer czy Supplier nie deklarują, że mogą rzucić wyjątek. W takim wypadku druga opcja zupełnie nam odpada.

Rozwiązanie

Rozwiązania w zasadzie są dwa:

Opakowanie wyjątku w RuntimeException

Weźmy sobie metodę, która rzuca wyjatkiem IOException:

public void throwsIOException() throws IOException {
    throw new IOException();
}
Teraz spróbujmy wywołać ją w wyrażeniu lambda:

public void throwInLambda() {
    Collections.singletonList(1).forEach(x -> throwsIOException());
}
Cóż... Kompilator nam na to nie pozwala. Opakujmy wyjątek w RuntimeException:

public void throwInLambdaWithRuntime() {
    Collections.singletonList(1).forEach(x -> {
        try {
            throwsIOException();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    });
}
Działa? Działa. Wyjątek jest przekazywany w górę do naszej metody throwInLambdaWithRuntime. Stąd można go obsłużyć lub przekazać wyżej. Problem w tym, że to tak naprawdę nie ten obiekt wyjątku o który nam chodziło. Docelowy obiekt znajduje się w cause. Traktuje to rozwiązanie jako połowiczne.

"Sneaky throws"

W Java 8 została wprowadzona nowa reguła wnioskowania, według której każde użycie throws T, gdzie T jest typem generycznym oznacza, że metoda może rzucić wyjątek typu RuntimeException. Dlaczego więc z tego nie skorzystać?
Przygotujmy sobie metodę sneakyThrow:

private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
    throw (T) t;
}
Do powyższej metody przekazujemy obiekt wyjątku, który chcemy rzucić. Zostanie on wtedy rzucony tak jak z użyciem słowa kluczowego throws ale kompilator nie będzie wymagał już jego przechwycenia. Weźmy sobie metodę rzucająca wyjątek z użyciem metody sneakyThrow:

public void throwsSneakyIOException() {
    sneakyThrow(new IOException("sneaky"));
}
I spróbujmy ją wykorzystać w wyrażeniu lambda:

public void sneakyThrowInLambda() {
    Collections.singletonList(1).forEach(x -> throwsSneakyIOException());
}
Kompilator nie zgłasza już żadnych problemów.
Rozwiązanie to ma jednak jedną złośliwość. No bo jeśli teraz zechcielibyśmy złapać wyjątek to nie mamy takiej możliwości bo nie jest on zadeklarowany. Zawsze możemy jednak wydzielić sobie fragment kodu do metody deklarującej rzucenie danego wyjątku i wtedy można go już złapać i obsłużyć.

O sneaky throws dowiedziałem się trochę przypadkiem przeglądając kod, korzystający z adnotacji biblioteki Lombok, którą bardzo polecam.

Dla mnie to odkrycie nieco zmniejsza ból głowy regularnie powodowany przez wszechobecne wszędzie wyjątki :)

Komentarze

Popularne posty z tego bloga

Aplikacja czasu rzeczywistego w Spring i AngularJS

Naszła mnie potrzeba aby utworzyć pewien dashboard, który reagowałby na zmiany przychodzące z serwera. Jako, że tworzę swoje aplikacje z wykorzystaniem Spring Boot (backend) i AngularJS (frontend) to o komunikacje serwer -> klient należy zadbać samemu. Po pospiesznej analizie tematu okazało się, że taką komunikację zapewnia protokół WebSockets (co dla niektórych może jest oczywiste, lecz dla mnie nie było). Co trzeba zrobić? Zestawić połączenie pomiędzy serwerem, a klientem i przesyłać wiadomości. Proste? Proste. No to jazda.
Projekt Kombinacja Spring Boot i AngularJS (angular w wersji 1) tak mi się spodobała, że utworzyłem sobie własny archetyp mavena, aby szybko móc tworzyć kolejne proste aplikacje. Jest on dostępny na moim GitHubie. Instrukcje tworzenia z niego projektu są w Readme, nie będę się powtarzał.
Eventy Taka aktualizacja danych musi być wywoływana przez jakieś zdarzenie. Na potrzeby przykładu wykorzystałem springowe eventy. Eventem w springu może być zwykłe POJO. W moi…

Immutable collections - Java

Mierzenie się z kolekcjami i poprawnym ich wykorzystaniem jest problematyczne dla początkujących programistów. Jak często widzieliście w jakimś kodzie fragment typu:
cart.getProducts().add(product); Sam pisałem niegdyś takie brzydkie rzeczy. W czasie, w którym wyrabiałem sobie moje skromne doświadczenie wypracowałem sobie jednak pewne zasady jak postępować z kolekcjami. Ktoś mógłby się ze mną spierać - nawet podając sensowne argumenty ale w mojej pracy trzymanie się tych nawyków sprawdza się i na razie się ich trzymam.
 W czym problem? Przedstawiony wyżej fragment kodu to wyraźne złamanie zasady enkapsulacji. Skoro obiekt udostępnia swoje wnętrze (w typ przypadku jakąś kolekcję) i można z tym robić wszystko co się nam spodoba to po co te wszystkie modyfikatory dostępu i po co w ogóle taki obiekt? Skoro obiekt, który posiada jako swoją właściwość (property) jakąś listę i udostępnia ją tak po prostu na zewnątrz to może okazać się, że biedny obiekcik jest nieświadomy, że ktoś mu grzebie w…

Dlaczego default jest blee... Wielodziedziczenie

Piszę ten post gdyż złapałem się na tym, że nie potrafiłem uargumentować swojego przekonania dotyczącego pewnego aspektu programowania gdy padło pytanie: "dlaczego?". Dopiero później wszystko sobie przypomniałem i mam nadzieję, że ten post nie pozwoli mi już o tym zapomnieć ;).

Dawno, dawno temu, gdy ukazała się Java w wersji 8, wielu z nas zachwycało się nowym mechanizmem: metody default! Bo to takie fajne, nowe, można ograniczyć powtarzanie się kodu, no generalnie bajer. Dopiero po chwili niestety, dotarło, że wprowadzenie takiego rozwiązania to cofanie się w czasie.
 Wielodziedziczenie Podczas lat rozwoju języków programowania pojawił się w pewnym momencie trend aby unikać stosowania rozwiązania zwanego wielodziedziczeniem. Czym jest ten mechanizm? Pozwala on na dziedziczenie z wielu klas bazowych jednocześnie. Unikanie jego stosowania, a nawet unikanie implementacji takiego mechanizmu w nowoczesnych językach jest poparte sensownymi argumentami.

Po pierwsze, może wydarzy…