2015-05-22 15 views
6

Problema: come posso lanciare l'eccezione e far sì che l'iterazione del flusso sappia che non deve interrompere l'intera iterazione, ma continuare con l'elemento successivo (e infine registrare gli oggetti non riusciti)?Come ripetere un flusso anche se vengono lanciate eccezioni?

+2

Se non vuoi restituire nulla quando viene lanciata un'eccezione, forse dovresti usare 'flatmap' invece di' map'. – Balduz

+1

Forse puoi semplicemente restituire null invece di una stringa e filtrare su oggetti non nulli? – Davio

+0

@membersound, è 'ParseException' qualcosa si è creato da soli, o' java.text.ParseException'? – aioobe

risposta

2

Senza un'eccezione si può lavorare con Optional:

stream.map(obj -> doMap(obj)) 
     .filter(obj -> obj.isPresent()) 
     .collect(Collectors.toList()); 

private Optional<String> doMap(Object obj) { 
    if (objectIsInvalid) { 
    return Optional.empty(); 
    } 
} 
+1

Suppongo che dipenda da cosa stai facendo, ma sopprimere silenziosamente le eccezioni di solito è una pessima idea. Se qualcuno ha passato dati non validi, o se l'utente ha specificato un input non valido, dovresti dirglielo, non comportarsi come se tutto fosse a posto. – VGR

+3

Non so perché si desidera "lavorare con nulls * e * Optionals". Normalmente dovresti decidere per * o *, usando 'null' o usando' Optional'. Il tuo codice restituisce 'null' in' doMap' e prova a richiamare '.isPresent()' su di esso all'interno di 'filter (...)' ... – Holger

+2

Per favore non farlo. Se qualcosa non è valido, probabilmente si desidera restituire un * vuoto * 'Opzionale' invece di' null'. Così com'è, otterrai NPE quando 'isPresent' è chiamato su' null'. –

2

Se si desidera un mezzo per ignorare tranquillamente quegli elementi che un'eccezione quando mappata, è possibile definire un metodo di supporto che o restituisce un successo o niente , come stream di 0 o 1 elementi, quindi flatMap il tuo stream con quel metodo.

import static java.util.stream.Collectors.toList; 

import java.util.List; 
import java.util.function.Supplier; 
import java.util.stream.Stream; 

public class Fish { 
    private int weight; 
    public Fish(int weight) { this.weight = weight; } 
    public String size() { 
     if (weight < 10) { 
      return "small"; 
     } else if (weight < 50) { 
      return "medium"; 
     } else if (weight < 100) { 
      return "large"; 
     } else { 
      throw new RuntimeException("I don't believe you"); 
     } 
    } 

    public static <T> Stream<T> successOrNothing(Supplier<T> supplier) { 
     try { 
     return Stream.of(supplier.get()); 
     } catch (Exception e) { 
     return Stream.empty(); 
     } 
    } 

    public static void main(String[] args) { 
     Stream<Fish> fishes = Stream.of(new Fish(1000), new Fish(2)); 
     List<String> sizes = fishes 
      .flatMap(fish -> successOrNothing(fish::size)) 
      .collect(toList()); 
     System.out.println(sizes); 
    } 
} 

Ogni elemento nel flusso viene mappato su 1 elemento (se la chiamata è tornato con successo) o 0 (se non ha). Questo potrebbe essere più semplice o più semplice della "modifica del metodo per restituire null o Optional.empty() anziché lanciare" pattern, specialmente quando si ha a che fare con un metodo il cui contratto è già definito e che non si desidera modificare.

Avvertenza: questa soluzione ignora silenziosamente le eccezioni; è inappropriato in molte situazioni.

3

Ecco uno strano trucco che è possibile utilizzare per migliorare la gestione delle eccezioni.

Diciamo che la vostra funzione mapper è come questo:

String doMap(Object obj) { 
    if (isInvalid(obj)) { 
     throw new IllegalArgumentException("info about obj"); 
    } else { 
     return obj.toString(); 
    } 
} 

Questo restituisce un risultato se l'oggetto è valida, ma viene generata un'eccezione se l'oggetto non è valido. Sfortunatamente se lo si configura direttamente in una pipeline, qualsiasi errore interromperà l'esecuzione della pipeline. Quello che vuoi è qualcosa come un tipo "o" che può contenere un valore o un indicatore di errore (che sarebbe un'eccezione in Java).

Risulta che CompletableFuture può contenere un valore o un'eccezione. Sebbene sia destinato all'elaborazione asincrona, che non si verifica qui, dobbiamo solo contenerlo un po 'per utilizzarlo per i nostri scopi.

Innanzitutto, dato un stream di oggetti da elaborare, chiamiamo la funzione di mappatura avvolto in una chiamata a supplyAsync:

CompletableFuture<String>[] cfArray = 
     stream.map(obj -> CompletableFuture.supplyAsync(() -> doMap(obj), Runnable::run)) 
       .toArray(n -> (CompletableFuture<String>[])new CompletableFuture<?>[n]); 

(Purtroppo, la creazione generico array dà un avvertimento incontrollato, che dovrà essere soppresso.)

il costrutto strana

CompletableFuture.supplyAsync(supplier, Runnable::run) 

corre il fornitore "asincrono" sulla condizione Esecutore Runnable::run, che esegue semplicemente l'attività immediatamente in questo thread. In altre parole, esegue il fornitore in modo sincrono.

Il trucco è che l'istanza CompletableFuture restituita da questa chiamata contiene il valore dal fornitore, se viene restituito normalmente o contiene un'eccezione, se il fornitore ne ha gettato uno. (Sto ignorando l'annullamento qui.) Quindi raccogliamo le istanze CompletableFuture in una matrice. Perché un array? È impostato per la parte successiva:

CompletableFuture.allOf(cfArray).join(); 

Questo normalmente attende che l'array di CF si completi.Dal momento che sono stati eseguiti in modo sincrono, dovrebbero già essere tutti completi. Ciò che è importante per questo caso è che join() genererà uno CompletionException se qualsiasi dei CF nell'array è stato completato in modo eccezionale. Se il join termina normalmente, possiamo semplicemente raccogliere i valori di ritorno. Se il join genera un'eccezione, possiamo propagarlo o possiamo prenderlo ed elaborare le eccezioni memorizzate nei CF dell'array. Ad esempio,

try { 
    CompletableFuture.allOf(cfArray).join(); 
    // no errors 
    return Arrays.stream(cfArray) 
       .map(CompletableFuture::join) 
       .collect(toList()); 
} catch (CompletionException ce) { 
    long errcount = 
     Arrays.stream(cfArray) 
       .filter(CompletableFuture::isCompletedExceptionally) 
       .count(); 
    System.out.println("errcount = " + errcount); 
    return Collections.emptyList(); 
} 

Se tutti hanno esito positivo, restituisce un elenco dei valori. Se ci sono delle eccezioni, questo conta il numero di eccezioni e restituisce una lista vuota. Naturalmente, si potrebbe facilmente fare qualcosa di diverso, come registrare i dettagli delle eccezioni, filtrare le eccezioni e restituire un elenco di valori validi, ecc

0

Farei seguente ...

stream.map(doMapLenient()) 
     .filter(Objects::nonNull) 
     .collect(Collectors.toList()); 

private String doMap(Object obj) { 
    if (objectIsInvalid(obj)) { 
    throw new ParseException("Object could not be parsed"); 
    } 
    return "Streams are cool"; 
} 

private Function<Object, String> doMapLenient() { 
    return obj -> { 
    try { 
     return doMap(obj); 
    } catch (ParseExcepion ex) { 
     return null; 
    } 
} 

qui puoi aggiungere anche un po 'di logging nella parte di cattura

Problemi correlati