Java 8: Jak pracować z metodami zgłaszania wyjątków w strumieniach?

172

Załóżmy, że mam klasę i metodę

class A {
  void foo() throws Exception() {
    ...
  }
}

Teraz chciałbym wywołać foo dla każdej instancji Adostarczonej przez strumień:

void bar() throws Exception {
  Stream<A> as = ...
  as.forEach(a -> a.foo());
}

Pytanie: Jak prawidłowo obsłużyć wyjątek? Kod nie kompiluje się na moim komputerze, ponieważ nie obsługuję możliwych wyjątków, które mogą być generowane przez foo (). throws ExceptionZ barwydaje się być bezużyteczne tutaj. Dlaczego?

Bastian
źródło
Związane z: stackoverflow.com/q/30117134/435605
AlikElzin-kilaka

Odpowiedzi:

141

Musisz zawinąć wywołanie metody w inne, w którym nie rzucasz sprawdzonych wyjątków . Nadal możesz wrzucić wszystko, co jest podklasą RuntimeException.

Normalny idiom zawijania to coś takiego:

private void safeFoo(final A a) {
    try {
        a.foo();
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

(Wyjątek nadtypu Exceptionjest używany tylko jako przykład, nigdy nie próbuj go złapać samodzielnie)

Następnie można wywołać go z: as.forEach(this::safeFoo).

skiwi
źródło
1
Jeśli chcesz, aby była to metoda opakowująca, zadeklarowałbym ją jako statyczną. Nie używa niczego z „tego”.
aalku
206
To smutne, że musimy to zrobić zamiast korzystać z naszych natywnych wyjątków ... o Java, daje, a potem zabiera
Erich,
1
@Stephan Ta odpowiedź została usunięta, ale nadal jest dostępna tutaj: stackoverflow.com/a/27661562/309308
Michael Mrozek
3
W mojej firmie oznaczałoby to natychmiastowe niepowodzenie przeglądu kodu: nie wolno nam rzucać niezaznaczonych wyjątków.
Stelios Adamantidis
7
@SteliosAdamantidis ta zasada jest niezwykle restrykcyjna i nieproduktywna. Czy zdajesz sobie sprawę, że wszystkie metody we wszystkich interfejsach API są uprawnione do rzucania wszystkich odmian wyjątków środowiska uruchomieniowego bez konieczności ich znajomości (oczywiście, że tak)? Czy zakazałeś javascript, ponieważ nie zaimplementowano koncepcji sprawdzonych wyjątków? Gdybym był Twoim głównym programistą, zamiast tego zakazałbym sprawdzonych wyjątków.
spi
35

Jeśli chcesz tylko wywołać fooi wolisz propagować wyjątek tak, jak jest (bez zawijania), możesz forzamiast tego po prostu użyć pętli Java (po przekształceniu strumienia w iterowalny z pewnymi sztuczkami ):

for (A a : (Iterable<A>) as::iterator) {
   a.foo();
}

To jest przynajmniej to, co robię w moich testach JUnit, w których nie chcę robić kłopotu z zawijaniem moich sprawdzonych wyjątków (i tak naprawdę wolę, aby moje testy rzucały niezapakowane oryginalne)

avandeursen
źródło
16

To pytanie może być trochę stare, ale ponieważ uważam, że „właściwa” odpowiedź jest tylko jednym sposobem, który może prowadzić do pewnych problemów ukrytych w dalszej części kodu. Nawet jeśli jest trochę kontrowersji , nie bez powodu istnieją zaznaczone wyjątki.

Moim zdaniem najbardziej elegancki sposób, jaki można znaleźć, podał Misha tutaj Zagreguj wyjątki środowiska uruchomieniowego w strumieniach Java 8, wykonując po prostu akcje w „futures”. Możesz więc uruchomić wszystkie działające części i zebrać niedziałające wyjątki jako jedną. W przeciwnym razie możesz zebrać je wszystkie na liście i przetworzyć je później.

Podobne podejście wywodzi Benji Weber . Sugeruje stworzenie własnego typu do zbierania części roboczych i niepracujących.

W zależności od tego, co naprawdę chcesz osiągnąć, proste mapowanie między wartościami wejściowymi a wartościami wyjściowymi wystąpiły Wyjątki mogą również działać dla Ciebie.

Jeśli nie podoba ci się żaden z tych sposobów, rozważ użycie (w zależności od oryginalnego wyjątku) przynajmniej własnego wyjątku.

Mariano
źródło
3
Powinno to być oznaczone jako poprawna odpowiedź. +. Ale nie mogę powiedzieć, że to dobry post. Powinieneś to bardzo rozwinąć. Jak własny wyjątek może pomóc? Jakie wystąpiły wyjątki? Dlaczego nie przyniosłeś tutaj różnych wariantów? Posiadanie całego przykładowego kodu w odwołaniach jest uważane za nawet zabroniony styl postu tutaj na SO. Twój tekst wygląda raczej jak zbiór komentarzy.
Gangnus,
2
Powinieneś być w stanie wybrać, na którym poziomie chcesz je złapać i strumieniować bałagan z tym. Interfejs API Stream powinien umożliwiać przenoszenie wyjątku do ostatniej operacji (takiej jak zbieranie) i być tam obsługiwany za pomocą programu obsługi lub w inny sposób. stream.map(Streams.passException(x->mightThrowException(x))).catch(e->whatToDo(e)).collect(...). Powinien spodziewać się wyjątków i pozwalać na traktowanie ich jak w przypadku przyszłości.
aalku
10

Proponuję użyć klasy Google Guava Throwables

Propagate ( Throwable Throwable)

Propaguje obiekt do rzucania w stanie takim, w jakim jest, jeśli jest to wystąpienie RuntimeException lub Error, lub w ostateczności zawija go w RuntimeException, a następnie propaguje. **

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            throw Throwables.propagate(e);
        }
    });
}

AKTUALIZACJA:

Teraz, gdy jest przestarzały, użyj:

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    });
}
yanefedor
źródło
4
Ta metoda została wycofana (niestety).
Robert Važan,
9

W ten sposób możesz zawijać i rozpakowywać wyjątki.

class A {
    void foo() throws Exception {
        throw new Exception();
    }
};

interface Task {
    void run() throws Exception;
}

static class TaskException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public TaskException(Exception e) {
        super(e);
    }
}

void bar() throws Exception {
      Stream<A> as = Stream.generate(()->new A());
      try {
        as.forEach(a -> wrapException(() -> a.foo())); // or a::foo instead of () -> a.foo()
    } catch (TaskException e) {
        throw (Exception)e.getCause();
    }
}

static void wrapException(Task task) {
    try {
        task.run();
    } catch (Exception e) {
        throw new TaskException(e);
    }
}
aalku
źródło
9

Możesz wykonać jedną z następujących czynności:

  • propagować sprawdzony wyjątek,
  • owinąć go i propagować niezaznaczony wyjątek lub
  • złap wyjątek i zatrzymaj propagację.

Kilka bibliotek pozwala to łatwo zrobić. Poniższy przykład został napisany przy użyciu mojej biblioteki NoException .

// Propagate checked exception
as.forEach(Exceptions.sneak().consumer(A::foo));

// Wrap and propagate unchecked exception
as.forEach(Exceptions.wrap().consumer(A::foo));
as.forEach(Exceptions.wrap(MyUncheckedException::new).consumer(A::foo));

// Catch the exception and stop propagation (using logging handler for example)
as.forEach(Exceptions.log().consumer(Exceptions.sneak().consumer(A::foo)));
Robert Važan
źródło
Dobra robota w bibliotece! Właśnie miałem napisać coś podobnego.
Jasper de Vries
2

Bardziej czytelny sposób:

class A {
  void foo() throws MyException() {
    ...
  }
}

Po prostu ukryj to, RuntimeExceptionaby to ominąćforEach()

  void bar() throws MyException {
      Stream<A> as = ...
      try {
          as.forEach(a -> {
              try {
                  a.foo();
              } catch(MyException e) {
                  throw new RuntimeException(e);
              }
          });
      } catch(RuntimeException e) {
          throw (MyException) e.getCause();
      }
  }

Chociaż w tym momencie nie będę się sprzeciwiać komuś, kto powie, że pomiń strumienie i pójdę z pętlą for, chyba że:

  • nie tworzysz swojego strumienia używając Collection.stream(), tj. nie jest to proste tłumaczenie do pętli for.
  • próbujesz użyć parallelstream()
Kashyap
źródło