Je veux créer une Map
à partir d'une List
sur Point
s et avoir dans la carte toutes les entrées de la liste mappées avec le même parentId tel que Map<Long, List<Point>>
.
J'ai utilisé Collectors.toMap()
mais il ne compile pas:
Map<Long, List<Point>> pointByParentId = chargePoints.stream()
.collect(Collectors.toMap(Point::getParentId, c -> c));
TLDR:
Pour collecter une Map
contenant une valeur unique par clé (Map<MyKey,MyObject>
), utilisez Collectors.toMap()
.
Pour rassembler dans une Map
qui contient plusieurs valeurs par clé (Map<MyKey, List<MyObject>>
), utilisez Collectors.groupingBy()
.
Collectors.toMap ()
En écrivant :
chargePoints.stream().collect(Collectors.toMap(Point::getParentId, c -> c));
L'objet retourné aura le type Map<Long,Point>
.
Regardez la fonction Collectors.toMap()
que vous utilisez:
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)
Il retourne une Collector
avec comme résultat Map<K,U>
où K
et U
sont le type de retour des deux fonctions passées à la méthode . Dans votre cas, Point::getParentId
est un long et c
se réfère à un Point
. Attendu que le Map<Long,Point>
retourné lorsque collect()
est appliqué sur.
Et ce comportement est plutôt attendu comme Collectors.toMap () javadoc déclare:
retourne une
Collector
qui accumule des éléments dans uneMap
dont les clés et les valeurs sont le résultat de l'application des fonctions de mappage fournies à l'entrée éléments.
Mais si les clés mappées contiennent des doublons (selon Object.equals(Object)
), une IllegalStateException
est levée
Ce sera probablement votre cas puisque vous regrouperez les Point
s selon une propriété spécifique: parentId
.
Si les clés mappées peuvent avoir des doublons, vous pouvez utiliser la surcharge toMap(Function, Function, BinaryOperator)
mais cela ne résoudra pas vraiment votre problème car cela ne groupera pas les éléments avec la même parentId
. Cela fournira juste un moyen de ne pas avoir deux éléments avec le même parentId
.
Collectors.groupingBy ()
Pour répondre à vos besoins, vous devez utiliser Collectors.groupingBy()
dont le comportement et la déclaration de méthode conviennent mieux à votre besoin:
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier)
Il est spécifié comme:
Retourne un collecteur implémentant une opération "groupe par" sur l'entrée éléments de type T, regroupement d’éléments selon une classification fonction et renvoyer les résultats dans une carte.
La méthode prend une Function
.
Dans votre cas, le paramètre Function
est Point
(le type
de Stream) et vous renvoyez Point.getParentId()
si vous souhaitez regrouper les éléments par les valeurs parentId
.
Pour que vous puissiez écrire:
Map<Long, List<Point>> pointByParentId =
chargePoints.stream()
.collect(Collectors.groupingBy( p -> p.getParentId()));
Ou avec une référence de méthode:
Map<Long, List<Point>> pointByParentId =
chargePoints.stream()
.collect(Collectors.groupingBy(Point::getParentId));
Collectors.groupingBy (): aller plus loin
En effet, le collecteur groupingBy()
va plus loin que l'exemple actuel. La méthode Collectors.groupingBy(Function<? super T, ? extends K> classifier)
n’est finalement qu’une méthode pratique pour stocker les valeurs de la Map
collectée dans une List
.
Pour stocker les valeurs de Map
dans autre chose que List
ou pour stocker le résultat d'un calcul spécifique, groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
devrait vous intéresser.
Par exemple :
Map<Long, Set<Point>> pointByParentId =
chargePoints.stream()
.collect(Collectors.groupingBy(Point::getParentId, toSet()));
Donc, au-delà de la question posée, vous devriez considérer groupingBy()
comme un moyen flexible de choisir les valeurs que vous souhaitez stocker dans la Map
collectée, ce que toMap()
ne correspond définitivement pas.
Collectors.groupingBy
est exactement ce que vous voulez, il crée une carte à partir de votre collection d'entrées, créant une entrée à l'aide de la Function
que vous fournissez pour sa clé et une liste de points avec la valeur de votre clé associée.
Map<Long, List<Point>> pointByParentId = chargePoints.stream()
.collect(Collectors.groupingBy(Point::getParentId));
Le code suivant fait le truc. Collectors.toList()
est la valeur par défaut, vous pouvez donc l'ignorer, mais au cas où vous souhaiteriez que Map<Long, Set<Point>>
Collectors.toSet()
soit nécessaire.
Map<Long, List<Point>> map = pointList.stream()
.collect(Collectors.groupingBy(Point::getParentId, Collectors.toList()));
Il est souvent vrai qu'une carte de object.field à Collection d'objets partageant ce champ est mieux stockée dans une carte multiple (Guava a une implémentation de Nice pour une carte multiple) . Si vous N'AVEZ PAS BESOIN de la carte multiple mutable (ce qui devrait être le cas désiré), vous pouvez utiliser
Multimaps.index(chargePoints, Point::getParentId);
Si vous devez utiliser une carte mutable, vous pouvez implémenter un collecteur (comme illustré ici: https://blog.jayway.com/2014/09/29/Java-8-collector-for-gauvas-linkedhashmultimap/ ) ou utilisez une boucle for (ou forEach) pour remplir une carte multimodale vide et modifiable.
Une carte multiple vous offre des fonctionnalités supplémentaires dont vous avez normalement besoin lorsque vous utilisez une carte d'un champ à une collection d'objets partageant un champ (comme le nombre total d'objets).
Une carte multimodale modifiable facilite également l'ajout et la suppression d'éléments à la carte (sans se préoccuper des cas Edge).