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.
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?
Nous examinerons comment mettre en œuvre chacun de ces éléments.
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à.
Gardez vavr disponible.
build.gradle
dependencies {
...
implementation 'io.vavr:vavr:0.9.3'
}
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.
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
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.
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.
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] **
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.
Recommended Posts