Java 8: Strumienie Lambda, filtrowanie według metody z wyjątkiem

168

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?

Martin Weber
źródło
1
Związane z: stackoverflow.com/questions/31637892/ ...
Marko Topolnik
4
Prosta i poprawna odpowiedź: złap wyjątek wewnątrz lambdy.
Brian Goetz

Odpowiedzi:

211

Musisz złapać wyjątek, zanim ucieknie on z lambda:

s = s.filter(a -> { try { return a.isActive(); } 
                    catch (IOException e) { throw new UncheckedIOException(e); }}});

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:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (RuntimeException e) { throw e; }
  catch (Exception e) { throw new RuntimeException(e); }
}

Twój przykład zostałby zapisany jako

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

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:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (Exception e) { return sneakyThrow(e); }
}
public static void uncheckRun(RunnableExc r) {
  try { r.run(); } catch (Exception e) { sneakyThrow(e); }
}
public interface RunnableExc { void run() throws Exception; }


@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

i możesz spodziewać się IOExceptionrzucenia w twarz, nawet jeśli collecttego 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ś IOExceptiondo 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.

Marko Topolnik
źródło
4
Widziałem już NettyIO wykonującego „podstępny rzut” i chciałem wyrzucić krzesło przez okno. - Co? Skąd ten sprawdzony wyjątek wyciekł? To pierwszy legalny przypadek użycia podstępnego rzutu, jaki widziałem. Jako programista musisz być czujny, jeśli chodzi o wskazanie, że podstępny rzut jest możliwy. Może lepiej jest po prostu utworzyć inny interfejs / plik implantu strumieniowego, który obsługuje zaznaczony wyjątek?
kevinarpe
8
Żadne rozsądne API nie powinno dostarczać klientowi niezadeklarowanych sprawdzonych wyjątków, to na pewno. W ramach API można jednak zrozumieć, że sprawdzony wyjątek może wyciekać. Nie powodują szkód, o ile są tylko kolejną oznaką ogólnego niepowodzenia i mają zostać złapane i hurtowo obsługiwane.
Marko Topolnik
5
@kevinarpe To jest dokładny powód, dla którego podstępne rzuty to zły pomysł. Skrócenie kompilatora może zmylić przyszłych opiekunów.
Thorbjørn Ravn Andersen
29
To, że nie lubisz zasad, nie oznacza, że ​​warto wziąć prawo w swoje ręce. Twoja rada jest nieodpowiedzialna, ponieważ stawia wygodę autora kodu ponad znacznie ważniejszymi względami przejrzystości i łatwości utrzymania programu.
Brian Goetz
34
@brian Tylko dlatego, że coś jest regułą, nie oznacza, że ​​to dobry pomysł. Ale jestem zaskoczony, że drugą część mojej odpowiedzi nazywasz „radą”, ponieważ pomyślałem, że jasno pokazałem, co proponuję jako rozwiązanie, a co oferuję zainteresowanemu czytelnikowi jako informacja do Twojej wiadomości, z obfitymi zastrzeżeniami.
Marko Topolnik
29

Możesz również propagować swój statyczny ból za pomocą lambd, aby całość wyglądała czytelnie:

s.filter(a -> propagate(a::isActive))

propagatetutaj otrzymuje java.util.concurrent.Callablejako parametr i konwertuje każdy wyjątek przechwycony podczas wywołania na RuntimeException. 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.

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}
Andrey Chaschev
źródło
22

Ta UtilExceptionklasa pomocnicza umożliwia używanie dowolnych zaznaczonych wyjątków w strumieniach Java, na przykład:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Uwaga Class::forNamerzuca ClassNotFoundException, co jest sprawdzane . Sam strumień również rzuca ClassNotFoundException, a NIE jakiś niezaznaczony wyjątek zawijania.

public final class UtilException {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Wiele innych przykładów, jak go używać (po zaimportowaniu statycznym UtilException):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }

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 UtilExceptionklasy można korzystać bez obaw. Jednak to zależy od Ciebie!

MarcG
źródło
8

Potencjalnie możesz rzucić swój własny Streamwariant, opakowując swoją lambdę, aby zgłosić niesprawdzony wyjątek, a następnie rozpakować ten niesprawdzony wyjątek w operacjach terminalowych:

@FunctionalInterface
public interface ThrowingPredicate<T, X extends Throwable> {
    public boolean test(T t) throws X;
}

@FunctionalInterface
public interface ThrowingFunction<T, R, X extends Throwable> {
    public R apply(T t) throws X;
}

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

public interface ThrowingStream<T, X extends Throwable> {
    public ThrowingStream<T, X> filter(
            ThrowingPredicate<? super T, ? extends X> predicate);

    public <R> ThrowingStream<T, R> map(
            ThrowingFunction<? super T, ? extends R, ? extends X> mapper);

    public <A, R> R collect(Collector<? super T, A, R> collector) throws X;

    // etc
}

class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> {
    private static class AdapterException extends RuntimeException {
        public AdapterException(Throwable cause) {
            super(cause);
        }
    }

    private final Stream<T> delegate;
    private final Class<X> x;

    StreamAdapter(Stream<T> delegate, Class<X> x) {
        this.delegate = delegate;
        this.x = x;
    }

    private <R> R maskException(ThrowingSupplier<R, X> method) {
        try {
            return method.get();
        } catch (Throwable t) {
            if (x.isInstance(t)) {
                throw new AdapterException(t);
            } else {
                throw t;
            }
        }
    }

    @Override
    public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) {
        return new StreamAdapter<>(
                delegate.filter(t -> maskException(() -> predicate.test(t))), x);
    }

    @Override
    public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) {
        return new StreamAdapter<>(
                delegate.map(t -> maskException(() -> mapper.apply(t))), x);
    }

    private <R> R unmaskException(Supplier<R> method) throws X {
        try {
            return method.get();
        } catch (AdapterException e) {
            throw x.cast(e.getCause());
        }
    }

    @Override
    public <A, R> R collect(Collector<T, A, R> collector) throws X {
        return unmaskException(() -> delegate.collect(collector));
    }
}

Następnie możesz użyć tego dokładnie w taki sam sposób, jak Stream:

Stream<Account> s = accounts.values().stream();
ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class);
return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());

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 Streamklasy (i nie tylko!).

Jeffrey
źródło
Cześć ... mały błąd? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
kevinarpe
1
@kevinarpe Yep. Powinien też był powiedzieć StreamAdapter.
Jeffrey
5

Użyj metody #propagate (). Przykładowa implementacja non-Guava z Blog Java 8 autorstwa Sama Berana :

public class Throwables {
    public interface ExceptionWrapper<E> {
        E wrap(Exception e);
    }

    public static <T> T propagate(Callable<T> callable) throws RuntimeException {
        return propagate(callable, RuntimeException::new);
    }

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}
n0mer
źródło
Link do bloga Java 8 nie działa.
Spycho
4

To nie odpowiada bezpośrednio na pytanie (istnieje wiele innych odpowiedzi), ale przede wszystkim stara się uniknąć problemu:

Z mojego doświadczenia Streamwynika, ż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ój Accountinterfejs jest doskonałym przykładem:

interface Account {
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
}

Zamiast rzucać IOExceptionna każdy getter, rozważ następujący projekt:

interface AccountReader {
    Account readAccount(…) throws IOException;
}

interface Account {
    boolean isActive();
    String getNumber();
}

Metoda AccountReader.readAccount(…)może odczytać konto z bazy danych, pliku lub czegokolwiek i zgłosić wyjątek, jeśli się nie powiedzie. Konstruuje Accountobiekt, który zawiera już wszystkie wartości, gotowy do użycia. Ponieważ wartości zostały już załadowane przez readAccount(…), 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):

  • Lepsze rozdzielenie obaw i przestrzeganie zasady pojedynczej odpowiedzialności
  • Mniej schematu: nie musisz zaśmiecać swojego kodu throws IOExceptionbez pożytku, ale aby zadowalać kompilator
  • Obsługa błędów: Obsługujesz błędy tam, gdzie się pojawiają - podczas odczytu z pliku lub bazy danych - zamiast gdzieś w środku logiki biznesowej tylko dlatego, że chcesz uzyskać wartość pól
  • Możesz to zrobić Account niezmiennym i czerpać korzyści z jego zalet (np. Bezpieczeństwa wątków)
  • Nie potrzebujesz „brudnych sztuczek” ani obejść do stosowania Accountw lambdach (np. W a Stream)
siegi
źródło
4

Można to rozwiązać za pomocą poniższego prostego kodu za pomocą Stream and Try in AbacusUtil :

Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();

Ujawnienie : Jestem twórcą AbacusUtil.

user_3380739
źródło
3

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 !!

@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
    boolean test(T t) throws E;
}

/**
 * .filter(rethrowPredicate(t -> t.isActive()))
 */
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
    return t -> {
        try {
            return predicate.test(t);
        } catch (Exception exception) {
            return throwActualException(exception);
        }
    };
}

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwActualException(Exception exception) throws E {
    throw (E) exception;
}

Następnie Twój przykład zostałby napisany w następujący sposób (dodanie testów, aby pokazać go wyraźniej):

@Test
public void testPredicate() throws MyTestException {
    List<String> nonEmptyStrings = Stream.of("ciao", "")
            .filter(rethrowPredicate(s -> notEmpty(s)))
            .collect(toList());
    assertEquals(1, nonEmptyStrings.size());
    assertEquals("ciao", nonEmptyStrings.get(0));
}

private class MyTestException extends Exception { }

private boolean notEmpty(String value) throws MyTestException {
    if(value==null) {
        throw new MyTestException();
    }
    return !value.isEmpty();
}

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}
PaoloC
źródło
Ten przykład się nie kompiluje
Roman M
Cześć @RomanM dzięki za wskazanie tego: poprawiłem brakujący typ zwracania w metodzie „throwActualException”. Używamy tego w produkcji, więc mam nadzieję, że działa również po twojej stronie.
PaoloC,
3

Aby poprawnie dodać kod obsługujący IOException (do RuntimeException), Twoja metoda będzie wyglądać następująco:

Stream<Account> s =  accounts.values().stream();

s = s.filter(a -> { try { return a.isActive(); } 
  catch (IOException e) { throw new RuntimeException(e); }});

Stream<String> ss = s.map(a -> { try { return a.getNumber() }
  catch (IOException e) { throw new RuntimeException(e); }});

return ss.collect(Collectors.toSet());

Problem polega teraz na tym, że IOExceptionwolę trzeba przechwycić jako a RuntimeExceptioni przekonwertować z powrotem na plikIOException - a to doda jeszcze więcej kodu do powyższej metody.

Po co używać, Streamskoro można to zrobić tak po prostu - a metoda rzuca, IOExceptionwięc nie jest do tego potrzebny żaden dodatkowy kod:

Set<String> set = new HashSet<>();
for(Account a: accounts.values()){
  if(a.isActive()){
     set.add(a.getNumber());
  } 
}
return set;
Koordynator
źródło
1

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:

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingFunction/

Grzegorz Piwowarek
źródło
1

Twój przykład można zapisać jako:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Unthrow klasy mogą być brane tutaj https://github.com/SeregaLBN/StreamUnthrower

SeregaLBN
źródło
0

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.

 s.filter(softenPredicate(a->a.isActive()));
John McClean
źródło
0

Funkcjonalne interfejsy w Javie nie deklarują żadnego zaznaczonego lub niezaznaczonego wyjątku. Musimy zmienić sygnaturę metod z:

boolean isActive() throws IOException; 
String getNumber() throwsIOException;

Do:

boolean isActive();
String getNumber();

Lub obsłuż to za pomocą bloku try-catch:

public Set<String> getActiveAccountNumbers() {
  Stream<Account> s =  accounts.values().stream();
  s = s.filter(a -> 
    try{
      a.isActive();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  Stream<String> ss = s.map(a -> 
    try{
      a.getNumber();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  return ss.collect(Collectors.toSet());
}

Inną opcją jest napisanie niestandardowego opakowania lub użycie biblioteki, takiej jak ThrowingFunction. Z biblioteką musimy tylko dodać zależność do naszego pom.xml:

<dependency>
    <groupId>pl.touk</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.3</version>
</dependency>

I użyj określonych klas, takich jak ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.

Na końcu kod wygląda następująco:

public Set<String> getActiveAccountNumbers() {
  return accounts.values().stream()
    .filter(ThrowingPredicate.unchecked(Account::isActive))
    .map(ThrowingFunction.unchecked(Account::getNumber))
    .collect(Collectors.toSet());
}
SHoko
źródło
0

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.

        Arrays.stream(VERSIONS)
        .map(version -> TemplateStore.class
                .getClassLoader().getResourceAsStream(String.format(TEMPLATE_FILE_MASK, version)))
        .map(inputStream -> {
            try {
                return ((EdiTemplates) JAXBContext.newInstance(EdiTemplates.class).createUnmarshaller()
                        .unmarshal(inputStream)).getMessageTemplate();
            } catch (JAXBException e) {
                throw new IllegalArgumentException(ERROR, e);
            }})
        .flatMap(Collection::stream)
        .collect(Collectors.toList());
atul sachan
źródło
Czy faktycznie próbujesz odpowiedzieć na pytanie?
Nilambar Sharma
@Nilambar - Próbuję tutaj powiedzieć, że nie ma sposobu na obsłużenie zaznaczonego wyjątku podczas korzystania ze strumienia java ... na końcu wszystkiego, czego potrzebujemy, aby złapać zaznaczony i rzucić jeden run-time / unchecked .. Teraz są dwie rzeczy: 1. jeśli uważasz, że nie mam racji z moim zrozumieniem, popraw mnie lub 2. jeśli uważasz, że mój post jest nieistotny, z przyjemnością go usunę. pozdrowienia, Atul
atul sachan
0

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.

@Test
public void whenValuePrinted_thenPrintValue() {

    List<Integer> intStream = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
    intStream.stream().map(Either.liftWithValue(item -> doSomething(item)))
             .map(item -> item.isLeft() ? item.getLeft() : item.getRight())
             .flatMap(o -> {
                 System.out.println(o);
                 return o.isPresent() ? Stream.of(o.get()) : Stream.empty();
             })
             .forEach(System.out::println);
}

private Object doSomething(Integer item) throws Exception {

    if (item == 0) {
        throw new Exception("Zero ain't a number!");
    } else if (item == 4) {
        return Optional.empty();
    }

    return item;
}
Steve Gelman
źródło