Java 8: Come lavoro con i metodi di lancio delle eccezioni nei flussi?


172

Supponiamo che io abbia una classe e un metodo

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

Ora vorrei chiamare foo per ogni istanza di Arecapitato da uno stream come:

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

Domanda: come gestisco correttamente l'eccezione? Il codice non viene compilato sulla mia macchina perché non gestisco le possibili eccezioni che possono essere generate da foo (). Il throws Exceptionof barsembra essere inutile qui. Perché?



Risposte:


141

È necessario racchiudere la chiamata del metodo in un'altra, in cui non si generano eccezioni verificate . Puoi comunque lanciare tutto ciò che è una sottoclasse di RuntimeException.

Un normale linguaggio avvolgente è qualcosa del tipo:

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

(L'eccezione Supertype Exceptionviene utilizzata solo come esempio, non tentare mai di rilevarla da soli)

Poi si può chiamare con: as.forEach(this::safeFoo).


1
Se vuoi che sia un metodo wrapper lo dichiarerei statico. Non usa nulla di 'questo'.
aalku,

207
È triste che dobbiamo fare questo invece di usare le nostre eccezioni native ... oh Java, dà e poi toglie
Erich,

1
@Stephan La risposta è stata eliminata, ma è ancora disponibile qui: stackoverflow.com/a/27661562/309308
Michael Mrozek,

3
Ciò fallirebbe istantaneamente la revisione del codice nella mia azienda: non ci è permesso di generare eccezioni non controllate.
Stelios Adamantidis,

7
@SteliosAdamantidis che regola è incredibilmente restrittivo e controproducente. sei consapevole che tutti i metodi, in tutte le API, sono autorizzati a lanciare tutti i tipi di eccezioni di runtime senza che tu debba nemmeno saperlo (ovviamente lo sei)? hai vietato javascript per non aver implementato il concetto di eccezioni controllate? Se fossi il tuo sviluppatore principale, vieterei invece le eccezioni verificate.
spi,

35

Se tutto ciò che vuoi è invocare fooe preferisci propagare l'eccezione così com'è (senza wrapping), puoi anche semplicemente usare il forloop di Java (dopo aver trasformato lo Stream in Iterable con qualche trucco ):

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

Questo è, almeno, quello che faccio nei miei test JUnit, in cui non voglio affrontare il problema di racchiudere le mie eccezioni verificate (e in effetti preferisco i miei test per gettare quelli originali non scartati)


16

Questa domanda potrebbe essere un po 'vecchia, ma poiché penso che la risposta "giusta" qui sia solo un modo che può portare ad alcuni problemi Problemi nascosti più avanti nel tuo codice. Anche se c'è un po 'di controversia , le eccezioni controllate esistono per un motivo.

Secondo me, il modo più elegante secondo me è stato dato da Misha qui Eccezioni di runtime aggregate nei flussi Java 8 semplicemente eseguendo le azioni in "futures". Quindi è possibile eseguire tutte le parti di lavoro e raccogliere eccezioni non funzionanti come una singola. Altrimenti potresti raccoglierli tutti in un elenco ed elaborarli in un secondo momento.

Un approccio simile viene da Benji Weber . Suggerisce di creare un proprio tipo per raccogliere parti funzionanti e non funzionanti.

A seconda di ciò che si desidera veramente ottenere una semplice mappatura tra i valori di input e i valori di output verificatisi, anche le eccezioni potrebbero funzionare.

Se non ti piace nessuno di questi modi, considera di utilizzare (a seconda dell'eccezione originale) almeno una propria eccezione.


3
Questo dovrebbe essere contrassegnato come una risposta corretta. +. Ma non posso dire che sia un buon post. Dovresti elaborarlo molto. In che modo può aiutare la propria eccezione? Quali sono le eccezioni occorse? Perché non hai portato qui le diverse varianti? Avere tutto il codice di esempio nei riferimenti è considerato uno stile di post persino proibito qui su SO. Il tuo testo sembra piuttosto un mucchio di commenti.
Gangnus,

2
Dovresti essere in grado di scegliere a quale livello vuoi catturarli e che i flussi lo rovinano. L'API Stream dovrebbe permetterti di portare l'eccezione fino all'operazione finale (come raccogliere) e di essere gestito lì con un gestore o essere lanciato in altro modo. stream.map(Streams.passException(x->mightThrowException(x))).catch(e->whatToDo(e)).collect(...). Si aspettano eccezioni e ti permettono di gestirle come fanno i futuri.
aalku,

10

Suggerisco di utilizzare la classe di Google Guava Throwables

propagano ( Throwable throwable)

Propaga gettabile così com'è se si tratta di un'istanza di RuntimeException o Error, oppure come ultima risorsa, lo avvolge in RuntimeException e quindi si propaga. **

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

AGGIORNARE:

Ora che è deprecato utilizzare:

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

4
Questo metodo è stato deprecato (purtroppo).
Robert Važan,

9

In questo modo puoi avvolgere e scartare le eccezioni.

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);
    }
}

9

Potresti voler eseguire una delle seguenti operazioni:

  • propagare l'eccezione controllata,
  • racchiuderlo e propagare un'eccezione non selezionata o
  • catturare l'eccezione e interrompere la propagazione.

Diverse librerie ti consentono di farlo facilmente. L'esempio che segue è scritto usando la mia libreria 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)));

Ottimo lavoro in biblioteca! Stavo per scrivere qualcosa di simile.
Jasper de Vries,

2

Modo più leggibile:

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

Basta nasconderlo in a RuntimeExceptionper farlo passareforEach()

  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();
      }
  }

Anche se a questo punto non mi opporrò a nessuno se dicono che salta i flussi e vai con un ciclo for, a meno che:

  • non stai creando il tuo stream utilizzando Collection.stream(), ovvero non la traduzione diretta in un ciclo for.
  • stai cercando di usare parallelstream()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.