Récursive utilisation de Flux de données.flatMap()
Considérer la classe suivante:
public class Order {
private String id;
private List<Order> orders = new ArrayList<>();
@Override
public String toString() {
return this.id;
}
//getters & setters
}
REMARQUE: Il est important de noter que je ne peut pas modifier cette classe, parce que je suis de les consommer, à partir d'une API externe.
Également envisager la hiérarchie suivante de commandes:
Order o1 = new Order();
o1.setId("1");
Order o11 = new Order();
o11.setId("1.1");
Order o111 = new Order();
o111.setId("1.1.1");
List<Order> o11Children = new ArrayList<>(Arrays.asList(o111));
o11.setOrders(o11Children);
Order o12 = new Order();
o12.setId("1.2");
List<Order> o1Children = new ArrayList<>(Arrays.asList(o11, o12));
o1.setOrders(o1Children);
Order o2 = new Order();
o2.setId("2");
Order o21 = new Order();
o21.setId("2.1");
Order o22 = new Order();
o22.setId("2.2");
Order o23 = new Order();
o23.setId("2.3");
List<Order> o2Children = new ArrayList<>(Arrays.asList(o21, o22, o23));
o2.setOrders(o2Children);
List<Order> orders = new ArrayList<>(Arrays.asList(o1, o2));
Qui pourrait être visuellement représentés de cette façon:
1
1.1
1.1.1
1.2
2
2.1
2.2
2.3
Maintenant, je veux aplatir cette hiérarchie des ordres dans un List
, de sorte que je reçois le texte suivant:
[1, 1.1, 1.1.1, 1.2, 2, 2.1, 2.2, 2.3]
J'ai réussi à le faire de manière récursive à l'aide de flatMap()
(avec une classe helper), comme suit:
List<Order> flattened = orders.stream()
.flatMap(Helper::flatten)
.collect(Collectors.toList());
C'est la classe helper:
public final class Helper {
private Helper() {
}
public static Stream<Order> flatten(Order order) {
return Stream.concat(
Stream.of(order),
order.getOrders().stream().flatMap(Helper::flatten)); //recursion here
}
}
La ligne suivante:
System.out.println(flattened);
Produit la sortie suivante:
[1, 1.1, 1.1.1, 1.2, 2, 2.1, 2.2, 2.3]
So far So good. Le résultat est absolument correcte.
Cependant, après la lecture de cette question, j'avais quelques inquiétudes concernant l'utilisation de flatMap()
à l'intérieur d'une méthode récursive. En particulier, je voulais savoir comment le flux a été élargi (si c'est le terme). J'ai donc modifié le Helper
classe et utilisé peek(System.out::println)
pour vérifier:
public static final class Helper {
private Helper() {
}
public static Stream<Order> flatten(Order order) {
return Stream.concat(
Stream.of(order),
order.getOrders().stream().flatMap(Helper::flatten))
.peek(System.out::println);
}
}
Et le résultat a été:
1
1.1
1.1
1.1.1
1.1.1
1.1.1
1.2
1.2
2
2.1
2.1
2.2
2.2
2.3
2.3
Je ne suis pas sûr si c'est la sortie qui doit être imprimé.
Alors, je me demande si c'est OK pour laisser intermédiaire flux contiennent les éléments répétés. En outre, quels sont les avantages et les inconvénients de cette approche? Est-il correct, après tout, à utiliser flatMap()
de cette façon? Est-il une meilleure façon d'obtenir les mêmes?
- Juste par curiosité: pourquoi avez-vous mis l'id après la création de l'ordre plutôt que d'en faire un argument du constructeur?
- Je ne peux pas modifier le
Order
de classe depuis qu'il parte d'une API, je suis de la consommer. - Ah, je vois. Alors la majeure partie de ma réponse est assez inutile. Il pourrait être intéressant d'ajouter cette information à votre question.
- Je vais modifier ça, merci.
Vous devez vous connecter pour publier un commentaire.
Bien, j'ai utilisé le même modèle avec un générique
Tree
classe et ne pas avoir un mauvais feeling avec elle. La seule différence, c'est que lesTree
classe elle-même offert unchildren()
etallDescendants()
méthodes, tous deux de retour d'unStream
et le dernier bâtiment sur l'ancien. Ceci est lié à “Dois-je retourner une Collection ou un cours d'eau?” et “Nommage des méthodes de java que les flux de retour”.À partir d'un
Stream
s point de vue, il n'y a pas de différence entre unflatMap
pour les enfants d'un autre type (c'est à dire lors de la traversée d'un bien) et unflatMap
aux enfants du même type. Il ya aussi pas de problème si le retour de flux de données contient le même élément nouveau, comme il n'y a pas de relation entre les éléments du flux. En principe, vous pouvez utiliserflatMap
comme unfilter
opération, à l'aide du modèleflatMap(x -> condition? Stream.of(x): Stream.empty())
. Il est également possible de l'utiliser pour dupliquer des éléments comme dans cette réponse.Il n'y a vraiment pas de problème avec vous à l'aide de
flatMap
de cette façon. Chacune des étapes intermédiaires dans un cours d'eau sont assez indépendantes (par conception) donc il n'y a pas de risques dans votre récursivité. La principale chose que vous devez regarder dehors pour est tout ce qui pourrait altérer la liste sous-jacente pendant que vous êtes en streaming. Dans votre cas, ce ne semble pas être un risque.L'idéal serait de faire cette récursivité partie de la
Order
classe elle-même:Ensuite, vous pouvez utiliser
orders.stream().flatMap(Order::streamOrders)
qui semble un peu plus naturel pour moi que d'utiliser une classe d'assistance.Comme une question d'intérêt, j'ai tendance à utiliser ces types de
stream
méthodes pour permettre l'utilisation de champs de la collection plutôt que d'un getter pour le champ. Si l'utilisateur de la méthode n'a pas besoin de savoir quelque chose au sujet de la collection sous-jacente ou le besoin d'être en mesure de le modifier puis le retour d'un flux est pratique et sûr.Je note qu'il y a un risque dans la structure de données que vous devez être conscient: une commande peut faire partie de plusieurs autres ordres, et peut-être même une partie de lui-même. Cela signifie qu'il est assez trivial à cause d'une récursion infinie et un débordement de pile:
Il ya beaucoup de bons modèles disponibles pour éviter ces sortes de questions, veuillez donc vous demander si vous voulez un peu d'aide dans ce domaine.
Vous point que vous ne pouvez pas modifier le
Order
classe. Dans ce cas, je vous suggère d'étendre à créer votre propre plus sûr de la version:C'est assez sûre en fonte, parce que vous attendez que les utilisateurs d'utiliser
addOrder
. Pas infaillible comme ils le pouvaient encore l'appelergetOrders
et ajouter unOrder
plutôt qu'unSafeOrder
. De nouveau, il existe des modèles pour prévenir que si vous êtes intéressé.