Mam problem z wypróbowaniem wyrażeń Lambda w Javie 8. Zwykle działa dobrze, ale teraz mam metody, które rzucają IOException
. Najlepiej, jeśli spojrzysz na następujący kod:
class Bank{
....
public Set<String> getActiveAccountNumbers() throws IOException {
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}
....
}
interface Account{
....
boolean isActive() throws IOException;
String getNumber() throws IOException;
....
}
Problem w tym, że nie kompiluje się, ponieważ muszę wychwycić możliwe wyjątki metod isActive- i getNumber. Ale nawet jeśli jawnie użyję try-catch-Block, jak poniżej, nadal nie kompiluje się, ponieważ nie łapię wyjątku. Więc albo jest błąd w JDK, albo nie wiem, jak wychwycić te wyjątki.
class Bank{
....
//Doesn't compile either
public Set<String> getActiveAccountNumbers() throws IOException {
try{
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}catch(IOException ex){
}
}
....
}
Jak to działa? Czy ktoś może podpowiedzieć mi właściwe rozwiązanie?
java
exception-handling
lambda
java-8
Martin Weber
źródło
źródło
Odpowiedzi:
Musisz złapać wyjątek, zanim ucieknie on z lambda:
Weź pod uwagę fakt, że lambda nie jest obliczana w miejscu, w którym ją zapisujesz, ale w jakimś zupełnie niepowiązanym miejscu, w klasie JDK. Więc to byłby punkt, w którym ten sprawdzony wyjątek zostałby zgłoszony i w tym miejscu nie zostałby zadeklarowany.
Możesz sobie z tym poradzić, używając opakowania swojej lambdy, które tłumaczy zaznaczone wyjątki na niezaznaczone:
Twój przykład zostałby zapisany jako
W swoich projektach zajmuję się tym zagadnieniem bez pakowania; zamiast tego używam metody, która skutecznie wyłącza sprawdzanie wyjątków przez kompilator. Nie trzeba dodawać, że należy się z tym obchodzić ostrożnie, a wszyscy uczestnicy projektu muszą mieć świadomość, że zaznaczony wyjątek może pojawić się, gdy nie zostanie zadeklarowany. To jest kod hydrauliczny:
i możesz spodziewać się
IOException
rzucenia w twarz, nawet jeślicollect
tego nie deklaruje. W większości przypadków, ale nie we wszystkich rzeczywistych przypadkach, i tak chciałbyś po prostu ponownie zgłosić wyjątek i traktować go jako ogólną awarię. We wszystkich tych przypadkach nic nie traci na jasności ani poprawności. Po prostu uważaj na te inne przypadki, w których naprawdę chciałbyś zareagować na wyjątek na miejscu. Deweloper nie zostanie poinformowany przez kompilator, że jest tam cośIOException
do złapania, a kompilator faktycznie będzie narzekał, jeśli spróbujesz go złapać, ponieważ oszukaliśmy go, wierząc, że nie można wyrzucić takiego wyjątku.źródło
Możesz również propagować swój statyczny ból za pomocą lambd, aby całość wyglądała czytelnie:
propagate
tutaj otrzymujejava.util.concurrent.Callable
jako parametr i konwertuje każdy wyjątek przechwycony podczas wywołania naRuntimeException
. Istnieje podobna metoda konwersji miotane # propagowane (miotane) w guawie.Ta metoda wydaje się być niezbędna do tworzenia łańcuchów metod lambda, więc mam nadzieję, że pewnego dnia zostanie dodana do jednej z popularnych bibliotek lub to zachowanie propagujące będzie domyślne.
źródło
Ta
UtilException
klasa pomocnicza umożliwia używanie dowolnych zaznaczonych wyjątków w strumieniach Java, na przykład:Uwaga
Class::forName
rzucaClassNotFoundException
, co jest sprawdzane . Sam strumień również rzucaClassNotFoundException
, a NIE jakiś niezaznaczony wyjątek zawijania.Wiele innych przykładów, jak go używać (po zaimportowaniu statycznym
UtilException
):Ale nie używaj go, zanim zrozumiesz następujące zalety, wady i ograniczenia :
• Jeśli kod wywołujący ma obsłużyć zaznaczony wyjątek, MUSISZ dodać go do klauzuli throws metody zawierającej strumień. Kompilator nie będzie już zmuszał Cię do dodawania go, więc łatwiej o tym zapomnieć.
• Jeśli kod wywołujący już obsługuje sprawdzony wyjątek, kompilator BĘDZIE przypominał o dodaniu klauzuli throws do deklaracji metody zawierającej strumień (jeśli tego nie zrobisz, powie: Wyjątek nigdy nie jest zgłaszany w treści odpowiedniej instrukcji try ).
• W każdym razie nie będziesz w stanie otoczyć samego strumienia, aby przechwycić zaznaczony wyjątek WEWNĄTRZ metody, która zawiera strumień (jeśli spróbujesz, kompilator powie: Wyjątek nigdy nie jest wyrzucany w treści odpowiedniej instrukcji try).
• Jeśli wywołujesz metodę, która dosłownie nigdy nie może zgłosić wyjątku, który deklaruje, nie powinieneś dołączać klauzuli throws. Na przykład: new String (byteArr, „UTF-8”) zgłasza wyjątek UnsupportedEncodingException, ale specyfikacja języka Java gwarantuje, że UTF-8 będzie zawsze obecny. W tym przypadku deklaracja rzutów jest uciążliwa i mile widziane jest wszelkie rozwiązanie, aby ją wyciszyć przy minimalnym schemacie.
• Jeśli nienawidzisz zaznaczonych wyjątków i uważasz, że nie powinny one być nigdy dodawane do języka Java na początku (coraz więcej osób myśli w ten sposób, a ja NIE jestem jednym z nich), po prostu nie dodawaj zaznaczonego wyjątku do throws klauzula metody zawierającej strumień. Zaznaczony wyjątek będzie wtedy zachowywał się jak wyjątek niezaznaczony.
• Jeśli implementujesz ścisły interfejs, w którym nie masz opcji dodania deklaracji rzucania, a mimo to zgłoszenie wyjątku jest całkowicie właściwe, wówczas zawijanie wyjątku tylko po to, aby uzyskać przywilej rzucania go, powoduje powstanie stosu śledzenia z fałszywymi wyjątkami które nie dostarczają żadnych informacji o tym, co faktycznie poszło nie tak. Dobrym przykładem jest Runnable.run (), która nie zgłasza żadnych sprawdzonych wyjątków. W takim przypadku możesz zdecydować, aby nie dodawać zaznaczonego wyjątku do klauzuli throws metody zawierającej strumień.
• W każdym przypadku, jeśli zdecydujesz się NIE dodawać (lub zapomnisz dodać) zaznaczonego wyjątku do klauzuli throws metody zawierającej strumień, pamiętaj o tych 2 konsekwencjach rzucania sprawdzonych wyjątków:
1) Kod wywołujący nie będzie w stanie przechwycić go według nazwy (jeśli spróbujesz, kompilator powie: Wyjątek nigdy nie jest zgłaszany w treści odpowiedniej instrukcji try). Będzie bąbelkowy i prawdopodobnie zostanie przechwycony w głównej pętli programu przez jakiś "catch Exception" lub "catch Throwable", co i tak może być tym, czego chcesz.
2) To narusza zasadę najmniejszego zaskoczenia: nie wystarczy już przechwycić RuntimeException, aby móc zagwarantować przechwycenie wszystkich możliwych wyjątków. Z tego powodu uważam, że nie powinno się tego robić w kodzie frameworkowym, ale tylko w kodzie biznesowym, nad którym masz pełną kontrolę.
Podsumowując: uważam, że ograniczenia tutaj nie są poważne, a z
UtilException
klasy można korzystać bez obaw. Jednak to zależy od Ciebie!źródło
Potencjalnie możesz rzucić swój własny
Stream
wariant, opakowując swoją lambdę, aby zgłosić niesprawdzony wyjątek, a następnie rozpakować ten niesprawdzony wyjątek w operacjach terminalowych:Następnie możesz użyć tego dokładnie w taki sam sposób, jak
Stream
:To rozwiązanie wymagałoby sporo szablonowego schematu, więc proponuję przyjrzeć się bibliotece, którą już stworzyłem, która robi dokładnie to, co opisałem tutaj dla całej
Stream
klasy (i nie tylko!).źródło
new StreamBridge<>(ts, IOException.class);
->new StreamBridge<>(s, IOException.class);
StreamAdapter
.Użyj metody #propagate (). Przykładowa implementacja non-Guava z Blog Java 8 autorstwa Sama Berana :
źródło
To nie odpowiada bezpośrednio na pytanie (istnieje wiele innych odpowiedzi), ale przede wszystkim stara się uniknąć problemu:
Z mojego doświadczenia
Stream
wynika, że potrzeba obsługi wyjątków w (lub innym wyrażeniu lambda) często wynika z faktu, że wyjątki są deklarowane jako wyrzucane z metod, w których nie powinny być wyrzucane. Często wynika to z połączenia logiki biznesowej z wejściem i wyjściem. TwójAccount
interfejs jest doskonałym przykładem:Zamiast rzucać
IOException
na każdy getter, rozważ następujący projekt:Metoda
AccountReader.readAccount(…)
może odczytać konto z bazy danych, pliku lub czegokolwiek i zgłosić wyjątek, jeśli się nie powiedzie. KonstruujeAccount
obiekt, który zawiera już wszystkie wartości, gotowy do użycia. Ponieważ wartości zostały już załadowane przezreadAccount(…)
, metody pobierające nie zgłosiłyby wyjątku. Dzięki temu można ich swobodnie używać w lambdach bez konieczności owijania, maskowania czy ukrywania wyjątków.Oczywiście nie zawsze można to zrobić tak, jak opisałem, ale często tak jest i prowadzi to do uzyskania czystszego kodu (IMHO):
throws IOException
bez pożytku, ale aby zadowalać kompilatorAccount
niezmiennym i czerpać korzyści z jego zalet (np. Bezpieczeństwa wątków)Account
w lambdach (np. W aStream
)źródło
Można to rozwiązać za pomocą poniższego prostego kodu za pomocą Stream and Try in AbacusUtil :
Ujawnienie : Jestem twórcą
AbacusUtil
.źródło
Rozszerzając rozwiązanie @marcg, możesz normalnie zgłosić i złapać sprawdzony wyjątek w strumieniach; to znaczy, kompilator poprosi cię o złapanie / ponowne wyrzucenie, tak jak byłeś poza strumieniami !!
Następnie Twój przykład zostałby napisany w następujący sposób (dodanie testów, aby pokazać go wyraźniej):
źródło
Aby poprawnie dodać kod obsługujący IOException (do RuntimeException), Twoja metoda będzie wyglądać następująco:
Problem polega teraz na tym, że
IOException
wolę trzeba przechwycić jako aRuntimeException
i przekonwertować z powrotem na plikIOException
- a to doda jeszcze więcej kodu do powyższej metody.Po co używać,
Stream
skoro można to zrobić tak po prostu - a metoda rzuca,IOException
więc nie jest do tego potrzebny żaden dodatkowy kod:źródło
Mając to na uwadze, opracowałem małą bibliotekę do obsługi sprawdzonych wyjątków i lambd. Niestandardowe adaptery umożliwiają integrację z istniejącymi typami funkcjonalnymi:
https://github.com/TouK/ThrowingFunction/
źródło
Twój przykład można zapisać jako:
Unthrow klasy mogą być brane tutaj https://github.com/SeregaLBN/StreamUnthrower
źródło
Jeśli nie masz nic przeciwko korzystaniu z bibliotek innych firm, cyklop-reakcja firmy AOL , ujawnienie :: Jestem współpracownikiem, ma klasę ExceptionSoftener, która może tu pomóc.
źródło
Funkcjonalne interfejsy w Javie nie deklarują żadnego zaznaczonego lub niezaznaczonego wyjątku. Musimy zmienić sygnaturę metod z:
Do:
Lub obsłuż to za pomocą bloku try-catch:
Inną opcją jest napisanie niestandardowego opakowania lub użycie biblioteki, takiej jak ThrowingFunction. Z biblioteką musimy tylko dodać zależność do naszego pom.xml:
I użyj określonych klas, takich jak ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.
Na końcu kod wygląda następująco:
źródło
Nie widzę żadnego sposobu obsługi zaznaczonego wyjątku w strumieniu (Java -8), jedyny sposób, w jaki zastosowałem, to złapanie sprawdzonego wyjątku w strumieniu i ponowne wyrzucenie go jako niezaznaczonego wyjątku.
źródło
Jeśli chcesz obsłużyć wyjątek w strumieniu i kontynuować przetwarzanie dodatkowych, w DZone jest doskonały artykuł autorstwa Briana Vermeera wykorzystujący koncepcję jednego z nich. Pokazuje doskonały sposób radzenia sobie z tą sytuacją. Brakuje tylko przykładowego kodu. To jest próbka mojej eksploracji z wykorzystaniem pojęć z tego artykułu.
źródło