2016-01-02 7 views
6

Nel codice seguente, quando cattura NumberFormatException fuori for iterazione, le stringhe in forma adeguata che appaiono nelle strList prima che il primo cattivo (vale a dire, "illegal_3") sono stati analizzati correttamente (ovvero, "1" e "2" sono stati analizzati come numeri interi 1 e 2).Catching eccezioni su 'flusso()' o 'parallelStream()' perde valori corretti

public void testCaughtRuntimeExceptionOutOfIteration() { 
    List<String> strList = Stream.of("1", "2", "illegal_3", "4", "illegal_5", "6").collect(Collectors.toList()); 
    List<Integer> intList = new ArrayList<>(); 

    try{ 
     for (String str : strList) { 
      intList.add(Integer.parseInt(str)); 
     } 
    } catch (NumberFormatException nfe) { 
     System.err.println(nfe.getMessage()); 
    } 

    List<Integer> expectedIntList = Stream.of(1, 2).collect(Collectors.toList()); 
    // passed 
    assertEquals("The first two elements have been parsed successfully.", expectedIntList, intList); 
} 

Tuttavia, quando si sostituisce for iterazione stream() o parallelStream(), perdo 1 e 2.

public void testCaughtRuntimeExceptionOutOfStream() { 
    List<String> strList = Stream.of("1", "2", "illegal_3", "4", "illegal_5", "6").collect(Collectors.toList()); 
    List<Integer> intList = new ArrayList<>(); 

    try{ 
     intList = strList.stream() // same with "parallelStream()" 
       .map(Integer::parseInt) 
       .collect(Collectors.toList()); 
    } catch (NumberFormatException nfe) { 
     System.err.println(nfe.getMessage()); 
    } 

    List<Integer> expectedIntList = Stream.of(1, 2).collect(Collectors.toList()); 
    // failed: expected:<[1,2]>, but was:<[]> 
    assertEquals("The first two elements have been parsed successfully.", expectedIntList, intList); 
} 

Qual è la specificazione del flusso di controllo di eccezioni sollevate da dentro o stream()parallelStream()?

Come posso ottenere il risultato di intList = [1,2] (vale a dire, ignorate quelle dopo la prima NumberFormatException è torta) o meglio ancora intList = [1,2,4,6] (vale a dire, ignorare i cattivi con NumberFormatException) con stream() o parallelStream()

+0

Ho appena stato a pensare stesse cose di ieri. +1 per una buona domanda – Andremoniy

+3

Ci sono molte domande correlate (troppe per elencarle qui e alcune potrebbero essere (almeno quasi) duplicate). La forma abbreviata: la specifica del flusso di controllo è sempre la stessa, indipendentemente dal fatto che stiate utilizzando o meno flussi. Se non vuoi che le eccezioni esplodano e interrompa il flusso di controllo, dovrai catturarle localmente. BTW: Nota che anche ** se ** hai lavorato attorno all'eccezione stessa: IIRC, il risultato con un 'parallelStream' potrebbe essere ancora' [2,1] '.... – Marco13

risposta

7

Perché non solo avvolgere il corpo lambda in try...catch?

Inoltre è possibile filtrare null valori dopo map:

intList = strList.stream()// same with "parallelStream()" 
      .map(x -> { 
       try { 
        return Integer.parseInt(x); 
       } catch (NumberFormatException nfe) { 
        System.err.println(nfe.getMessage()); 
       } 
       return null; 
      }) 
      .filter(x -> x!= null) 
      .collect(Collectors.toList()); 

Questo vi darà desiderato intList = [1,2,4,6].

Modifica: per ridurre la "pesantezza" di un tentativo/cattura in un lamdba è possibile aggiungere un metodo di supporto.

static Integer parseIntOrNull(String s) { 
    try { 
     return Integer.parseInt(s); 
    } catch (NumberFormatException nfe) { 
     System.err.println(nfe.getMessage()); 
    } 
    return null; 
} 

intList = strList.stream() 
      .map(x -> parseIntOrNull(x)) 
      .filter(x -> x!= null) 
      .collect(Collectors.toList()); 

O, per evitare l'uso di null, è possibile restituire un flusso

static Stream<Integer> parseIntStream(String s) { 
    try { 
     return Stream.of(Integer.parseInt(s)); 
    } catch (NumberFormatException nfe) { 
     System.err.println(nfe.getMessage()); 
    } 
    return Stream.empty(); 
} 

intList = strList.stream() 
      .flatMap(x -> parseIntStream(x)) 
      .collect(Collectors.toList()); 
+0

Sì, funziona. Grazie. (Tuttavia, penso che il blocco 'try ... catch' sia troppo * pesante * per essere in' lambda-body'.) – hengxin

+1

@hengxin troppo pesante? perché? Non lo rende pesante in alcun modo. È errato presupposto ... – Andremoniy

+1

"Pesante" sintatticamente; nient'altro. È una buona soluzione. – hengxin

2

Un metodo non può sia restituire un valore, e un'eccezione. Questo è impossibile.

Quindi non è possibile aspettarsi che sia collect() restituire un elenco e generare un'eccezione. Poiché se genera un'eccezione, non può restituire una nuova lista.

Se il codice ciclo era in realtà simile al codice torrente, si avrebbe lo stesso problema:

public void testCaughtRuntimeExceptionOutOfIteration() { 
    List<String> strList = Stream.of("1", "2", "illegal_3", "4", "illegal_5", "6").collect(Collectors.toList()); 
    List<Integer> intList = new ArrayList<>(); 

    try{ 
     intList = collectToIntegers(strList); 
    } catch (NumberFormatException nfe) { 
     System.err.println(nfe.getMessage()); 
    } 

    List<Integer> expectedIntList = Stream.of(1, 2).collect(Collectors.toList()); 
    // fails 
    assertEquals("The first two elements have been parsed successfully.", expectedIntList, intList); 
} 

private List<Integer> collectToIntegers(List<String> strList) { 
    List<Integer> result = new ArrayList<>(); 
    for (String str : strList) { 
     result.add(Integer.parseInt(str)); 
    } 
    return result; 
} 

In breve: non confondere "la creazione e restituendo una nuova lista", con "prendere un elenco e aggiungere elementi ad esso ".

+0

C'è work-aroung con valore 'nullo' e predicato' filtro' ... – Andremoniy

1

Non so quante volte ho riscontrato una situazione in cui volevo semplicemente ignorare NumberFormatException. Probabilmente creerei un metodo riutilizzabile separato per analizzare il numero intero in modo silenzioso e restituire il valore di OptionInt.

Ecco la classe utils

public class IntUtils { 
    // ... other utility methods 

    public static OptionalInt parseInt(String s, Consumer<? super Exception> exceptionConsumer) { 
     try { 
      return OptionalInt.of(Integer.parseInt(s)); 
     } catch (NumberFormatException e) { 
      if (exceptionConsumer != null) { 
       // let the caller take the decision 
       exceptionConsumer.accept(e); 
      } else { 
       // default behavior 
       e.printStackTrace(); 
      } 
     } 

     return OptionalInt.empty(); 
    } 

    public static OptionalInt parseInt(String s) { 
     return parseInt(s, null); 
    } 
} 

Ecco il metodo di prova

List<Integer> collect1 = strStream.map(str -> IntUtils.parseInt(str, Exception::printStackTrace)) 
      .filter(OptionalInt::isPresent) 
      .map(OptionalInt::getAsInt).collect(toList()); 

// or 
List<Integer> collect2 = strStream.map(IntUtils::parseInt) 
      .filter(OptionalInt::isPresent) 
      .map(OptionalInt::getAsInt).collect(toList()); 
+0

https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/primitives/Ints. html # TryParse-java.lang.String- –

0

Sto usando org.apache.commons.lang3.math.NumberUtils:

.mapToInt(s -> NumberUtils.toInt(s, Integer.MIN_VALUE)) 
.filter(x -> x > Integer.MIN_VALUE)