Java 8 list to map with stream

2019-01-17 09:45发布

问题:

I have a List<Item> collection. I need to convert it into Map<Integer, Item> The key of the map must be the index of the item in the collection. I can not figure it out how to do this with streams. Something like:

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

Any help?

As this question is identified as possible duplicate I need to add that my concrete problem was - how to get the position of the item in the list and put it as a key value

回答1:

You can create a Stream of the indices using an IntStream and then convert them to a Map :

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


回答2:

One more solution just for completeness is to use custom collector:

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());

This solution is parallel-friendly and does not depend on the source (you can use list without random access or Files.lines() or whatever).



回答3:

Don't feel like you have to do everything in/with the stream. I would just do:

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

As long as you don't parallelise the stream this will work and it avoids potentially expensive and/or problematic (in the case of duplicates) get() and indexOf() operations.

(You cannot use a regular int variable in place of the AtomicInteger because variables used from outside a lambda expression must be effectively final. Note that when uncontested (as in this case), AtomicInteger is very fast and won't pose a performance problem. But if it worries you you can use a non-thread-safe counter.)



回答4:

This is updated answer and has none of the problems mentioned in comments.

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


回答5:

Using a third party library (protonpack for example, but there are others) you can zip the value with its index and voila:

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

although getIndex returns a long, so you may need to cast it using something similar to:

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


回答6:

Eran's answer is usually the best approach for random-access lists.

If your List isn't random access, or if you have a Stream instead of a List, you can use forEachOrdered:

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

This is safe, if the stream is parallel, even though the destination map is thread-unsafe and is operated upon as a side effect. The forEachOrdered guarantees that items are processed one-at-a-time, in order. For this reason it's unlikely that any speedup will result from running in parallel. (There might be some speedup if there are expensive operations in the pipeline before the forEachOrdered.)