Gestire le eccezioni con i flussi


10

Ho un Map<String,List<String>>e voglio che si trasformi in Map<String,List<Long>>perché ognuno Stringnell'elenco rappresenta un Long:

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(Long::valueOf)
                                                      .collect(toList()))
               );

Il mio problema principale è che ognuno Stringpotrebbe non rappresentare correttamente a Long; potrebbe esserci qualche problema. Long::valueOfpuò sollevare eccezioni. In questo caso, desidero restituire un valore nullo o vuotoMap<String,List<Long>>

Perché voglio iterare dopo su questa outputmappa. Ma non posso accettare alcuna conversione di errore; nemmeno uno solo. Qualche idea su come posso restituire un output vuoto in caso di stringa errata -> Conversione lunga?


Sono d'accordo con la soluzione Naman ma purtroppo nel blocco catch, non riesco a recuperare la chiave (Entry :: getKey) per la quale la stringa -> Conversione lunga non è corretta
AntonBoarf

Discussione simile qui: String to int - probabilmente i dati errati devono evitare eccezioni in cui alla fine ho deciso di effettuare un controllo preliminare con regex (i documenti parseLong usano le stesse regole di analisi e probabilmente vorrai restituire un LongStreamse hai intenzione di rimuovere i emptyrisultati)
AjahnCharles

Scusate ho frainteso. Pensavo intendessi restituire una singola voce come vuota / nulla; ma ora penso che intendi l'intera mappa!
AjahnCharles,

1
Non è del tutto chiaro quale sia il punto principale: vuoi restituire una mappa vuota in caso di errore, ma stampare comunque la "chiave" in cui l'errore è apparso sulla console? Voglio dire, le informazioni sul contesto in cui l'eccezione è apparso è di solito trasportato lo stack di chiamate nel l'eccezione. Indipendentemente da ciò: hai specificamente chiesto informazioni sui flussi, ma consiglio vivamente di evitare le chiamate "collezionate" nidificate. Le persone che devono sostenerlo in seguito (e questo potrebbe essere il tuo futuro !) Si chiederanno che cosa hai fatto lì. Almeno introdurre alcuni metodi di supporto correttamente denominati.
Marco13,

Risposte:


4

Che ne dici di un esplicito catcholtre l'eccezione:

private Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    try {
        return input.entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                        .map(Long::valueOf)
                        .collect(Collectors.toList())));
    } catch (NumberFormatException nfe) {
        // log the cause
        return Collections.emptyMap();
    }
}

ok suona bene ... ma nel catch (nfe) vorrei recuperare il valore specifico di key (Entry :: getKey) e String errato per il quale non riesce in modo da poter accedere con precisione dove va storto. È possibile ?
AntonBoarf,

@AntonBoarf Se si desidera semplicemente registrare la chiave, per la quale non è stato possibile analizzare la stringa, utilizzarenfe.getMessage()
Naman

1
@AntonBoarf il messaggio dell'eccezione conterrà la stringa di input non valida. Per ottenere la chiave responsabile, farei una ricerca esplicita, solo quando si è verificata l'eccezione, ad esempioinput.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger

@Titolare. Grazie ... Sembra complicato ... Mi chiedo se usare Standart per il ciclo Java5 non sia migliore nel mio caso
AntonBoarf

@AntonBoarf implementa entrambi e confronta ...
Holger,

3

Personalmente mi piace fornire un Optionalinput sull'analisi dei numeri:

public static Optional<Long> parseLong(String input) {
    try {
        return Optional.of(Long.parseLong(input));
    } catch (NumberFormatException ex) {
        return Optional.empty();
    }
}

Quindi, usando il tuo codice (e ignorando l' input errato):

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(MyClass::parseLong)
                                                      .filter(Optional::isPresent)
                                                      .map(Optional::get)
                                                      .collect(toList()))
               );

Inoltre, considera un metodo di supporto per renderlo più conciso:

public static List<Long> convertList(List<String> input) {
    return input.stream()
        .map(MyClass::parseLong).filter(Optional::isPresent).map(Optional::get)
        .collect(Collectors.toList());
}

public static List<Long> convertEntry(Map.Entry<String, List<String>> entry) {
    return MyClass.convertList(entry.getValue());
}

Quindi puoi filtrare i risultati nel raccoglitore del tuo stream:

Map<String, List<Long>> converted = input.entrySet().stream()
    .collect(Collectors.toMap(Entry::getKey, MyClass::convertEntry));

Potresti anche mantenere gli Optionaloggetti vuoti nei tuoi elenchi, e quindi confrontando il loro indice nel nuovo List<Optional<Long>>(anziché List<Long>) con l'originale List<String>, puoi trovare la stringa che ha causato input errati. Potresti anche semplicemente accedere a questi erroriMyClass#parseLong

Tuttavia, se il tuo desiderio è quello di non operare su alcun input negativo, circondare l'intero flusso in ciò che stai tentando di catturare (secondo la risposta di Naman) è il percorso che prenderei.


2

Puoi creare una StringBuilderchiave for con eccezione e verificare se eleè numerica come di seguito,

 public static Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    StringBuilder sb = new StringBuilder();
    try {
    return input.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                    .map(ele->{
                        if (!StringUtils.isNumeric(ele)) {
                            sb.append(e.getKey()); //add exception key
                            throw new NumberFormatException();
                        }
                        return Long.valueOf(ele);
                    })
                    .collect(Collectors.toList())));
} catch (NumberFormatException nfe) {
    System.out.println("Exception key "+sb);
    return Collections.emptyMap();
}
}

Spero che sia d'aiuto.


0

Può essere che tu possa scrivere un metodo helper in grado di verificare la presenza di valori numerici nella stringa e filtrarli dallo stream e anche valori null quindi infine raccogliere sulla mappa.

// StringUtils.java
public static boolean isNumeric(String string) {
    try {
        Long.parseLong(string);
        return true;
    } catch(NumberFormatException e) {
        return false;
    }
}

Questo si prenderà cura di tutto.

E usa questo nel tuo stream.

Map<String, List<Long>> newMap = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> mapToLongValues(entry.getValue())));

public List<Long> mapToLongValues(List<String> strs) {
    return strs.stream()
        .filter(Objects::nonNull)
        .filter(StringUtils::isNumeric)
        .map(Long::valueOf)
        .collect(Collectors.toList());
}
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.