Ausnahmen mit Streams behandeln

10

Ich habe eine Map<String,List<String>>und möchte, dass daraus wird, Map<String,List<Long>>weil jede Stringin der Liste eine darstellt 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()))
               );

Mein Hauptproblem ist, dass jeder Stringmöglicherweise nicht richtig a darstellt Long; Möglicherweise liegt ein Problem vor. Long::valueOfkann Ausnahmen auslösen. In diesem Fall möchte ich eine Null oder ein Leerzeichen zurückgebenMap<String,List<Long>>

Weil ich danach über diese outputKarte iterieren möchte . Ich kann jedoch keine Fehlerkonvertierung akzeptieren. nicht einmal ein einziger. Haben Sie eine Idee, wie ich bei falscher Zeichenfolge -> lange Konvertierung eine leere Ausgabe zurückgeben kann?

AntonBoarf
quelle
Ich stimme der Naman-Lösung zu, aber leider kann ich im catch-Block den Schlüssel (Entry :: getKey) nicht abrufen, für den die Konvertierung von String -> Long falsch ist
AntonBoarf
Ähnliche Diskussion hier: String to int - Wahrscheinlich müssen schlechte Daten Ausnahmen vermeiden, bei denen ich mich schließlich entschlossen habe, mit Regex vorab zu prüfen (parseLong-Dokumente verwenden dieselben Parsing-Regeln und Sie möchten wahrscheinlich a zurückgeben, LongStreamwenn Sie emptyErgebnisse entfernen möchten )
AjahnCharles
Entschuldigung, ich habe es falsch verstanden. Ich dachte, Sie wollten einen einzelnen Eintrag als leer / null zurückgeben. aber jetzt denke ich du meinst die gesamte Karte!
AjahnCharles
1
Es ist nicht ganz klar, worum es geht - Sie möchten im Fehlerfall eine leere Karte zurückgeben und trotzdem den "Schlüssel" drucken, auf dem der Fehler auf der Konsole aufgetreten ist? Ich meine, Informationen über den Kontext, in dem die Ausnahme aufgetreten ist, werden normalerweise über den Aufrufstapel in der Ausnahme transportiert. Unabhängig davon: Sie haben speziell nach Streams gefragt, aber ich würde dringend empfehlen, verschachtelte "Sammeln" -Anrufe zu vermeiden. Leute, die das später behaupten müssen (und das könnte auch Ihre Zukunft sein !), Werden sich fragen, was zum Teufel Sie dort gemacht haben. Führen Sie zumindest einige richtig benannte Hilfsmethoden ein.
Marco13

Antworten:

4

Wie wäre es mit einem expliziten catchüber die Ausnahme:

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();
    }
}
Naman
quelle
ok hört sich gut an ... aber im catch (nfe) möchte ich den spezifischen Wert von key (Entry :: getKey) und den falschen String abrufen, für den es fehlschlägt, damit ich genau protokollieren kann, wo es schief geht. Ist es möglich ?
AntonBoarf
@AntonBoarf Wenn Sie nur den Schlüssel protokollieren möchten, für den der String nicht analysiert werden konnte, verwenden Sienfe.getMessage()
Naman
1
@AntonBoarf Die Nachricht der Ausnahme enthält die fehlerhafte Eingabezeichenfolge. Um den verantwortlichen Schlüssel zu erhalten, würde ich eine explizite Suche nur dann durchführen, wenn die Ausnahme aufgetreten ist, z. B.input.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger
@Halter. Danke ... Das scheint kompliziert zu sein ... Ich frage mich, ob die Verwendung von Standard für die Schleife Java5 in meinem Fall nicht besser ist
AntonBoarf
@AntonBoarf einfach beide implementieren und vergleichen ...
Holger
3

Ich persönlich möchte einen OptionalBeitrag zum Parsen von Zahlen liefern :

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

Verwenden Sie dann Ihren eigenen Code (und ignorieren Sie schlechte Eingaben):

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

Ziehen Sie außerdem eine Hilfsmethode in Betracht, um dies prägnanter zu gestalten:

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

Anschließend können Sie die Ergebnisse im Collector Ihres Streams filtern:

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

Sie können die leeren OptionalObjekte auch in Ihren Listen behalten. Wenn Sie dann ihren Index im neuen List<Optional<Long>>(statt im List<Long>) mit dem Original vergleichen List<String>, können Sie die Zeichenfolge finden, die zu fehlerhaften Eingaben geführt hat. Sie können diese Fehler auch einfach anmeldenMyClass#parseLong

Wenn Sie jedoch den Wunsch haben, überhaupt keine schlechten Eingaben zu verarbeiten, ist es der Weg, den ich einschlagen würde, den gesamten Stream in dem zu umgeben, was Sie zu fangen versuchen (gemäß Namans Antwort).

Schurke
quelle
2

Sie können StringBuildermit Ausnahme einen for-Schlüssel erstellen und prüfen, ob er wie elefolgt numerisch ist.

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

Ich hoffe es hilft.

Code_Mode
quelle
0

Möglicherweise können Sie eine Hilfsmethode schreiben, mit der die Zeichenfolge auf numerische Werte überprüft und aus dem Stream herausgefiltert werden kann. Außerdem können Nullwerte gesammelt und schließlich in der Map erfasst werden.

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

Dies wird sich um alles kümmern.

Und verwenden Sie dies in Ihrem 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());
}
TheTechMaddy
quelle