Gestion des exceptions avec les flux

10

J'ai un Map<String,List<String>>et je veux qu'il se transforme en Map<String,List<Long>>parce que chacun Stringdans la liste représente 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()))
               );

Mon principal problème est que chacun Stringpeut ne pas représenter correctement un Long; il peut y avoir un problème. Long::valueOfpeut soulever des exceptions. Si tel est le cas, je veux retourner une valeur nulle ou videMap<String,List<Long>>

Parce que je veux répéter après cette outputcarte. Mais je ne peux accepter aucune conversion d'erreur; pas même un seul. Une idée de la façon dont je peux retourner une sortie vide en cas de chaîne incorrecte -> Conversion longue?

AntonBoarf
la source
Je suis d'accord avec la solution Naman mais malheureusement dans le bloc catch, je n'arrive pas à récupérer la clé (Entry :: getKey) pour laquelle la chaîne -> Conversion longue est incorrecte
AntonBoarf
Discussion similaire ici: String to int - probablement de mauvaises données doivent éviter les exceptions où j'ai finalement décidé de pré-vérifier avec regex (les documents parseLong utilisent les mêmes règles d'analyse et vous voudrez probablement retourner un LongStreamsi vous prévoyez de supprimer les emptyrésultats)
AjahnCharles
Désolé j'ai mal compris. Je pensais que vous vouliez renvoyer une seule entrée vide / nulle; mais maintenant je pense que vous voulez dire la carte entière!
AjahnCharles
1
Ce n'est pas tout à fait clair quel est le point principal - vous voulez retourner une carte vide en cas d'erreur, mais toujours imprimer la "clé" où l'erreur est apparue à la console? Je veux dire, les informations sur le contexte dans lequel l'exception est apparue sont généralement transportées vers le haut de la pile d'appels dans l'exception. Indépendamment de cela: vous avez spécifiquement posé des questions sur les flux, mais je vous recommande fortement d'éviter les appels imbriqués de "collecte". Les gens qui doivent le maintenir plus tard (et cela pourrait bien être votre futur !) Se demanderont ce que vous avez fait là-bas. Introduisez au moins quelques méthodes d'assistance correctement nommées.
Marco13

Réponses:

4

Que diriez-vous d'un explicite catchsur l'exception:

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
la source
ok sonne bien ... mais dans la capture (nfe), je voudrais récupérer la valeur spécifique de la clé (Entry :: getKey) et la chaîne incorrecte pour laquelle il échoue afin que je puisse me connecter précisément où il va mal. C'est possible ?
AntonBoarf du
@AntonBoarf Si vous souhaitez simplement enregistrer la clé, pour laquelle il n'a pas pu analyser la chaîne, utiliseznfe.getMessage()
Naman
1
@AntonBoarf, le message de l'exception contiendra la chaîne d'entrée mal formée. Pour obtenir la clé responsable, je ferais une recherche explicite, uniquement lorsque l'exception s'est produite, par exempleinput.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger
@Titulaire. Merci ... Cela semble compliqué ... Je me demande si l'utilisation de standart pour la boucle Java5 n'est pas meilleure dans mon cas
AntonBoarf
@AntonBoarf implémente les deux et compare…
Holger
3

Personnellement, j'aime fournir une Optionalentrée sur l'analyse des nombres:

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

Ensuite, en utilisant votre propre code (et en ignorant les mauvaises entrées):

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

En outre, envisagez une méthode d'assistance pour rendre cela plus succinct:

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

Ensuite, vous pouvez filtrer les résultats dans le collecteur de votre flux:

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

Vous pouvez également conserver les Optionalobjets vides dans vos listes, puis en comparant leur index dans le nouveau List<Optional<Long>>(au lieu de List<Long>) avec l'original List<String>, vous pouvez trouver la chaîne qui a causé des entrées erronées. Vous pouvez également simplement enregistrer ces échecs dansMyClass#parseLong

Cependant, si votre désir est de ne pas opérer sur une mauvaise entrée, entourer tout le flux dans ce que vous essayez d'attraper (selon la réponse de Naman) est la route que je prendrais.

Coquin
la source
2

Vous pouvez créer une StringBuilderclé for avec exception et vérifier si elle eleest numérique comme ci-dessous,

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

J'espère que cela aide.

Code_Mode
la source
0

Peut-être que vous pouvez écrire une méthode d'assistance qui peut vérifier la valeur numérique dans la chaîne et les filtrer du flux ainsi que les valeurs nulles, puis collecter finalement sur la carte.

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

Cela prendra soin de tout.

Et utilisez-le dans votre flux.

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
la source