web-dev-qa-db-fra.com

Comment convertir List <V> en Map <K, List <V>>, avec Java 8 flux et fournisseurs de List et Map personnalisés?

Il est facile de convertir List<V> en Map<K, List<V>>. Par exemple:

public Map<Integer, List<String>> getMap(List<String> strings) {
   return
      strings.stream()
             .collect(Collectors.groupingBy(String::length));
}

Mais je veux le faire avec mes propres List et Mapfournisseurs.

Je suis venu avec ceci:

public Map<Integer, List<String>> getMap(List<String> strings) {
   return strings.stream()
       .collect(Collectors.toMap(
             String::length,
             item -> {List<String> list = new ArrayList<>(); list.add(item); return list;},
             (list1, list2) -> {list1.addAll(list2); return list1;},
             HashMap::new));
}

Question: Existe-t-il un moyen plus facile, moins verbeux ou plus efficace de le faire? Par exemple, quelque chose comme ça (qui ne fonctionne pas):

return strings.stream()
      .collect(Collectors.toMap(
            String::length,
            ArrayList::new,                    
            HashMap::new));

Et si je n'ai besoin que de définir le fournisseur List, mais pas le fournisseur Map?

22
MarcG

Vous pourriez avoir les éléments suivants:

public Map<Integer, List<String>> getMap(List<String> strings) {
    return strings.stream().collect(
      Collectors.groupingBy(String::length, HashMap::new, Collectors.toCollection(ArrayList::new))
    );
}

Le collecteur groupingBy(classifier, mapFactory, downstream) peut être utilisé pour spécifier quel type de carte est souhaité, en lui passant un fournisseur de la carte souhaitée pour le mapFactory. Ensuite, le collecteur en aval, qui est utilisé pour collecter les éléments regroupés dans la même clé, est toCollection(collectionFactory) , ce qui permet de collecter dans une collection obtenue auprès du fournisseur donné.

Cela garantit que la carte renvoyée est un HashMap et que les listes, dans chaque valeur, sont ArrayList. Notez que si vous souhaitez renvoyer des implémentations spécifiques de map et de collection, vous souhaiterez probablement que la méthode renvoie également ces types spécifiques, afin que vous puissiez utiliser leurs propriétés.

Si vous souhaitez uniquement spécifier un fournisseur de collecte et conserver la carte par défaut groupingBy, vous pouvez simplement omettre le fournisseur dans le code ci-dessus et utiliser surcharge de deux arguments :

public Map<Integer, List<String>> getMap(List<String> strings) {
    return strings.stream().collect(
      Collectors.groupingBy(String::length, Collectors.toCollection(ArrayList::new))
    );
}

En remarque, vous pourriez avoir une méthode générique pour cela:

public <K, V, C extends Collection<V>, M extends Map<K, C>> M getMap(List<V> list,
        Function<? super V, ? extends K> classifier, Supplier<M> mapSupplier, Supplier<C> collectionSupplier) {
    return list.stream().collect(
        Collectors.groupingBy(classifier, mapSupplier, Collectors.toCollection(collectionSupplier))
    );
}

L'avantage de cette déclaration est que vous pouvez maintenant l'utiliser pour avoir comme résultat HashMap de ArrayLists spécifiques, ou LinkedHashMap de LinkedListss, si l'appelant le souhaite il:

HashMap<Integer, ArrayList<String>> m = getMap(Arrays.asList("foo", "bar", "toto"),
        String::length, HashMap::new, ArrayList::new);
LinkedHashMap<Integer, LinkedList<String>> m2 = getMap(Arrays.asList("foo", "bar", "toto"),
        String::length, LinkedHashMap::new, LinkedList::new);

mais, à ce stade, il peut être plus simple d'utiliser directement le groupingBy dans le code ...

28
Tunaki

J'avais une situation similaire. Je l'ai résolu comme:

Map<String, List<Object>> map = stringList.stream().collect(Collectors.toMap(str -> str, str -> populateList(str)));

Et populateList() est:

private List<Object> populateList(final String str) {
    ...
    ....
    List<Object> list = // dao.get(str);
    return list;
}
0
Arvind SJ