Tirez parti de l'un ou l'autre pour la gestion des exceptions individuelles dans l'API Java Stream

L'API Stream de Java est très utile, mais la gestion des exceptions est fastidieuse.

Il existe des moyens de le remplacer par une exception d'exécution dans le lambda ou d'utiliser Optional, mais je veux le gérer un peu plus correctement. Je veux utiliser l'un ou l'autre comme monade parce que c'est un gros problème. Pour le moment, Either n'est pas implémenté par défaut, mais il semble que cela puisse être fait en utilisant la bibliothèque vavr. Il semble que le nom de la bibliothèque qui s'appelait autrefois javaslang a changé.

Soit est un conteneur qui contient deux éléments dans Right / Left comme Tuple, et en maintenant une valeur normale dans Right et une erreur dans Left, vous pouvez connecter le traitement dans une chaîne comme Optional. Facultatif et Soit les monades apprises de Java8 Stream.

Donc, je voudrais organiser comment utiliser Either avec Stream API.

Modèles de gestion des exceptions individuelles dans Stream

Si vous faites quelque chose sur chaque élément du flux et qu'une exception se produit dans ce processus, que voulez-vous faire avec cette exception? Pour le moment, pensez-vous aux trois modèles suivants?

  1. Je veux ignorer tous les éléments qui ont soulevé l'exception et extraire uniquement les éléments réussis
  2. Je veux attraper la première exception
  3. Je veux attraper toutes les exceptions qui se sont produites

Nous examinerons comment mettre en œuvre chacun de ces éléments.

0. Préparation préalable

Exemple de code

Voici un exemple de code pour réfléchir à ce problème. Prend une liste de chaînes comme argument et crée une liste d'entiers avec Integer # parseInt entre les deux.

Test.java


public void test() {
    List<String> list = Arrays.asList("123", "abc", "45", "def", "789");

    List<Integer> result = list.stream()   //Make it Stream
            .map(this::parseInt)           //Convertir des éléments individuels
            .collect(Collectors.toList()); //Revenir à la liste comme résiliation
}

private Integer parseInt(String data) {
    try {
        return Integer.parseInt(value);
    } catch (Exception ex) {
        return null; //Je resserre l'erreur et la rend nulle
    }
}

Cet exemple réduit l'erreur et renvoie null, donc [123, null, 45, null, 789] Vous obtiendrez une liste comme celle-là.

Importer vavr

Gardez vavr disponible.

build.gradle


dependencies {
    ...
    implementation 'io.vavr:vavr:0.9.3'
}

1. Extraire uniquement les éléments réussis

Ni l'un ni l'autre n'est nécessaire car il ne gère pas l'erreur dans ce cas.

FilterTest.java


public void testFilterSuccess() {
    List<String> data = Arrays.asList("123", "abc", "45", "def", "789");

    // List<Integer>
    var result = data.stream()
            .map(this::parseIntOpt)
            .filter(Optional::isPresent).map(Optional::get)
            .collect(Collectors.toList());
}

private Optional<Integer> parseIntOpt(String value) {
    try {
        return Optional.of(Integer.parseInt(value));
    } catch (Exception ex) {
        return Optional.empty();
    }
}

Filtrez uniquement les éléments qui ont des valeurs avec filtre et récupérez-les avec la carte pour terminer.

2. Extrayez la première exception

Le sujet principal est d'ici.

Tout d'abord, modifiez le traitement de l'élément pour qu'il renvoie l'un ou l'autre.

EitherTest.java


private Either<Exception, Integer> parseIntEither(String value) {
    try {
        return Either.right(Integer.parseInt(value));
    } catch (Exception ex) {
        return Either.left(ex);
    }
}

Modification de l'exemple pour utiliser cette fonction

EitherTest.java


public void testFirstException() {
    List<String> data = Arrays.asList("123", "abc", "45", "def", "789");

    // List<Either<Exception, Integer>>
    var result = data.stream()
            .map(this::parseIntEither)
            .collect(Collectors.toList()));
}

À ce stade, il s'agit toujours d'une liste de l'un ou l'autre. S'il y a une erreur, nous voulons extraire la première erreur, et si c'est normal, nous voulons obtenir List <Integer>, nous allons donc effectuer une conversion supplémentaire. Ici, la méthode statique Either # sequenceRight implémentée dans vavr est utilisée.

EitherTest.java


// Either<Exception, Seq<Integer>>
var result = Either.sequenceRight(data.stream()
        .map(this::parseIntEither)
        .collect(Collectors.toList()));

Cette méthode applique la liste de l'un ou l'autre dans l'ordre, et s'il y a ʻEither.left () , elle conserve sa valeur, et si tout est ʻEither.right (), elle l'agrège ʻEither.right (Seq ) `Sera retourné. Tout ce que vous avez à faire est de récupérer la valeur de l'un ou l'autre.

EitherTest.java


try {
    List<Integer> intList = result.getOrElseThrow(ex -> ex).toJavaList();
    // handle intList
} catch (Exception ex) {
    // handle exception
}

Puisque Seq est une classe Sequence définie dans vavr, elle est convertie en liste Java, mais s'il n'y a pas de problème, il est possible de continuer le traitement avec Seq.

Donc, en résumé, le code est le suivant.

EitherTest.java


public void testFirstException() {
    List<String> data = Arrays.asList("123", "abc", "45", "def", "789");

    try {
        List<Integer> result = Either.sequenceRight(data.stream()
                .map(this::parseIntEither)
                .collect(Collectors.toList()))
                .getOrElseThrow(ex -> ex).toJavaList();
    } catch (Exception ex) {
        //L'erreur ci-dessous est lancée
        // For input string: "abc"
    }
}

Je sens que je peux l'écrire très bien.

3. Extrayez toutes les exceptions qui se produisent

C'est la même chose que 2. jusqu'au milieu. Si vous utilisez Either # sequence au lieu de Either # sequenceRight, il collectera toutes les exceptions et retournera Seq.

EitherTest.java


// Either<Seq<Exception>, Seq<Integer>>
var result = Either.sequence(data.stream()
        .map(this::parseIntEither)
        .collect(Collectors.toList()));

En utilisant cela, si vous rassemblez la liste d'exceptions collectée avec un pli, vous pouvez écrire comme suit.

EitherTest.java


public void testAllException() {
    List<String> data = Arrays.asList("123", "abc", "45", "def", "789");

    try {
        List<Integer> result = Either.sequence(data.stream()
                .map(this::parseIntEither)
                .collect(Collectors.toList()))
                .getOrElseThrow(exSeq -> exSeq.fold(new Exception("Multiple Exception"),
                        (root, value) -> {
                            root.addSuppressed(value);
                            return root;
                        }))
                .toJavaList();
    } catch (Exception ex) {
        // ex.getSuppressed()À"abc,"def"L'exception contenant 2 exceptions est levée
    }
}

Maintenant, vous pouvez également regrouper les exceptions pour chaque élément.

Extra: extraire respectivement la liste des exceptions et la liste des nombres entiers (supplémentaire)

L'une ou l'autre est une classe qui prend toujours l'état Left ou Right, donc s'il y a une exception, c'est isLeft (), sinon c'est isRight (). Par conséquent, s'il y a une exception, il n'est pas possible de collecter la valeur normale lors de l'extraction de l'exception. Je ne sais pas s'il existe une telle utilisation, mais à des fins d'étude, je vais également examiner comment séparer List <Sither <Exception, Integer >> en List <Exception> et List <Integer>. ..

L'approche consiste à terminer Stream

collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner)

Collectez dans Tuple2 <List <Exception>, List <Integer >> de vavr en utilisant.

TupleTest.java


//Tuple2<List<Exception>, List<Integer>>
var result = data.stream()
        .map(this::parseIntEither)
        .collect(() -> Tuple.of(new ArrayList<Exception>(), new ArrayList<Integer>()),
                (accumulator, value) -> {
                    if (value.isLeft()) {
                        accumulator._1.add(value.getLeft());
                    } else {
                        accumulator._2.add(value.get());
                    }
                },
                (left, right) -> {
                    left._1.addAll(right._1);
                    left._2.addAll(right._2);
                });

Ce n'est pas très beau si vous écrivez jusqu'à présent. .. Si vous voulez vraiment l'implémenter, vous pouvez créer une classe distincte qui implémente l'interface Collector.

** [Une addition] **

Dans les commentaires, il m'a appris à écrire très simplement.

collect(Collectors.collectingAndThen(
  Collectors.groupingBy(Either::isLeft), 
  map -> Tuple.of(map.get(true),map.get(false))
);

Collectors.groupingBy regroupe les éléments Stream selon le processus spécifié et renvoie une carte de (Clé de regroupement-> Liste d'éléments). Dans ce cas, nous obtenons Map <Boolean, Seq <Either <Exception, Integer >>> en regroupant selon que Either :: isLeft est vrai ou faux.

De plus, Collectors.collectingAndThen peut décrire le processus de collecte dans le premier argument et le processus suivant dans le deuxième argument. Ainsi, le deuxième argument prend la valeur de map et la convertit en Tuple.

Si vous l'organisez comme les autres exemples, vous obtiendrez le code suivant.

TupleTest2.java


var result = data.stream()
        .map(this::parseIntEither)
        .collect(Collectors.collectingAndThen(
                Collectors.groupingBy(Either::isLeft),
                map -> Tuple.of(
                        Either.sequence(map.get(true)).getLeft().toJavaList(),
                        Either.sequence(map.get(false)).get().toJavaList())
        ));

Le code est clairement plus propre. La méthode Collectors est pratique, n'est-ce pas? Je veux garder ça bas.

** [Ajout ici] **

Résumé

J'ai eu le sentiment que la gestion des exceptions de Stream, qui était ennuyeuse, pouvait être écrite assez proprement. Je m'inquiétais de savoir comment organiser proprement les deux pendant un certain temps, mais bien sûr, une méthode pratique a été mise en œuvre dans vavr. vavr pratique.

référence

Recommended Posts

Tirez parti de l'un ou l'autre pour la gestion des exceptions individuelles dans l'API Java Stream
Essayez d'utiliser l'API Stream en Java
ChatWork4j pour l'utilisation de l'API ChatWork en Java
API Java Stream en 5 minutes
Techniques de gestion des exceptions en Java
[java8] Pour comprendre l'API Stream
Analyser l'analyse syntaxique de l'API COTOHA en Java
Appelez l'API de notification Windows en Java
Gestion des exceptions Java?
API Java Stream
[Java] Gestion des exceptions
☾ Java / Gestion des exceptions
À propos de la gestion des exceptions Java
Gestion des exceptions Java
[Java] Gestion des Java Beans dans la chaîne de méthodes
Essayez d'utiliser l'analyse syntaxique de l'API COTOHA en Java
Questions sur la gestion des exceptions Java throw et try-catch
[Java] API / carte de flux
Utiliser des expressions Java lambda en dehors de l'API Stream
Journaux d'erreurs et gestion des exceptions qui sont souvent vus dans la zone Java
Remarque sur l'initialisation des champs dans le didacticiel Java
Pratique de l'API Java8 Stream
API Zabbix en Java
J'ai traduit [Méthode Clone pour les tableaux Java] comme méthode Clone dans les tableaux Java.
[À voir absolument pour l'apprenti ingénieur Java] Comment utiliser l'API Stream
Conseils d'utilisation de Salesforce SOAP et de l'API Bulk en Java
[Java] Pratique de la gestion des exceptions [Exception]
Aide-mémoire de l'API Java Stream
[Java] À propos de la gestion des exceptions try-catch
[Ruby] Gestion des exceptions dans les fonctions
Règles d'utilisation pour la gestion des exceptions Java
[Java] Stream API - Traitement de l'arrêt du flux
[Java] Stream API - Traitement intermédiaire de flux
[Java] Introduction à l'API Stream
Application Java pour les débutants: stream
[Java] Opération intermédiaire de l'API Stream
Examinons la signification de "stream" et "collect" dans l'API Stream de Java.
Exemple de code pour appeler l'API Yahoo! Local Search en Java
Accéder à l'interface réseau avec Java
[Introduction à Java] À propos de l'API Stream
[Session d'étude interne] Gestion des exceptions Java (2017/04/26)
Spécifiez l'emplacement Java dans eclipse.ini
Décompressez le fichier zip en Java
Générer l'URL de l'API CloudStack en Java
Hit l'API de Zaim (OAuth 1.0) en Java
J'ai essayé d'utiliser l'API Java8 Stream
Paramètres de débogage SSL dans Java
Conseils pour la gestion des énumérations avec thymeleaf
JPA (API de persistance Java) dans Eclipse
Utilisez-vous Stream en Java?
Conseils pour gérer les pseudo-éléments dans Selenium
Appelez la super méthode en Java
J'ai appelé l'analyse de la syntaxe de l'API COTOHA 100 fois en Java pour mesurer les performances.
[Communication Socket (Java)] Impressions de la mise en œuvre de la communication Socket dans la pratique pour la première fois
[Java] Générez une liste restreinte à partir de plusieurs listes à l'aide de l'API Stream
À vous à qui on a dit "Ne pas utiliser Stream API" dans le champ
Première programmation de ma vie Java 1st Hello World