web-dev-qa-db-fra.com

Java 8 liste à mapper avec le flux

J'ai un List<Item> collection. J'ai besoin de le convertir en Map<Integer, Item> La clé de la carte doit être l’index de l’élément de la collection. Je ne peux pas comprendre comment faire cela avec des flux. Quelque chose comme:

items.stream().collect(Collectors.toMap(...));

De l'aide?

Comme cette question est identifiée comme une duplication possible, je dois ajouter que mon problème concret était de savoir comment obtenir la position de l'élément dans la liste et la définir comme valeur clé.

49
Nikolay

Vous pouvez créer un Stream des index en utilisant un IntStream, puis les convertir en un Map:

Map<Integer,Item> map = 
    IntStream.range(0,items.size())
             .boxed()
             .collect(Collectors.toMap (i -> i, i -> items.get(i)));
50
Eran

Une autre solution, par souci d'exhaustivité, consiste à utiliser un collecteur personnalisé:

public static <T> Collector<T, ?, Map<Integer, T>> toMap() {
    return Collector.of(HashMap::new, (map, t) -> map.put(map.size(), t), 
            (m1, m2) -> {
                int s = m1.size();
                m2.forEach((k, v) -> m1.put(k+s, v));
                return m1;
            });
}

Usage:

Map<Integer, Item> map = items.stream().collect(toMap());

Cette solution est compatible avec les parallèles et ne dépend pas de la source (vous pouvez utiliser list sans accès aléatoire ou Files.lines() ou autre).

12
Tagir Valeev

Ne vous sentez pas obligé de faire tout dans/avec le flux. Je voudrais juste faire:

AtomicInteger index = new AtomicInteger();
items.stream().collect(Collectors.toMap(i -> index.getAndIncrement(), i -> i));

Tant que vous ne paralléliserez pas le flux, cela fonctionnera et évitera des opérations potentiellement coûteuses et/ou problématiques (dans le cas de doublons) get() et indexOf().

(Vous ne pouvez pas utiliser une variable int régulière à la place de AtomicInteger, car les variables utilisées en dehors d'une expression lambda doivent être effectivement définitives. Notez que lorsque non contesté (comme dans ce cas), AtomicInteger est très rapide et ne pose pas de problème de performances, mais si cela vous inquiète, vous pouvez utiliser un compteur non thread-safe.)

9
Pepijn Schmitz

Ceci est la réponse mise à jour et n'a aucun des problèmes mentionnés dans les commentaires.

Map<Integer,Item> outputMap = IntStream.range(0,inputList.size()).boxed().collect(Collectors.toMap(Function.identity(), i->inputList.get(i)));
7
i_am_zero

réponse d'Eran est généralement la meilleure approche pour les listes à accès aléatoire.

Si votre List n’est pas un accès aléatoire, ou si vous avez un Stream au lieu d’un List, vous pouvez utiliser forEachOrdered:

Stream<Item> stream = ... ;
Map<Integer, Item> map = new HashMap<>();
AtomicInteger index = new AtomicInteger();
stream.forEachOrdered(item -> map.put(index.getAndIncrement(), item));

Ceci est sûr, si le flux est parallèle, même si la carte de destination est thread-unsafe et est utilisée comme un effet secondaire. Le forEachOrdered garantit que les éléments sont traités un par un, dans l'ordre. Pour cette raison, il est improbable que l'exécution en parallèle entraîne une accélération. (Il peut y avoir une certaine accélération si des opérations coûteuses sont en cours avant le forEachOrdered.)

1
Stuart Marks

En utilisant une bibliothèque tierce partie ( protonpack par exemple, mais il y en a d'autres), vous pouvez Zip la valeur avec son index et le tour est joué:

StreamUtils.zipWithIndex(items.stream())
    .collect(Collectors.toMap(Indexed::getIndex, Indexed::getValue));

bien que getIndex renvoie un long, vous devrez peut-être le lancer à l'aide de quelque chose de similaire à:

i -> Integer.valueOf((int) i.getIndex())
1
njzk2