我得到了一些自定义对象的流,并且我想创建一个映射Map<Integer, MyObject>
,并将每个对象的索引作为键。给你一个简单的例子:
Stream<String> myStream = Arrays.asList("one","two","three").stream();
Integer i = 0;
Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> i++, x -> x));
很显然,它不能编译,因为:
从Lambda表达式引用的局部变量必须是final或 有效地最终
是否有一种简单的方法可以将流的元素映射到它们的索引,以便上述示例的预期输出类似于:
{1=one, 2=two, 3=three}
答案 0 :(得分:11)
您可以使用IntStream
来解决此问题:
None
您从List<String> list = Arrays.asList("one","two","three");
Map<Integer, String> map = IntStream.range(0, list.size()).boxed()
.collect(Collectors.toMap(Function.identity(), list::get));
到IntStream
创建一个0
(IntStream.range()
从流中排除最后一个值),并将每个索引映射到列表中的值。此解决方案的优点是,它也可以与并行流一起使用,而使用list.size() - 1
则不可能。
因此在这种情况下的结果将是:
AtomicInteger
要在{0=one, 1=two, 2=three}
开始第一个索引,只需在收集过程中添加1
:
1
这将导致以下结果:
List<String> list = Arrays.asList("one", "two", "three");
Map<Integer, String> map = IntStream.range(0, list.size()).boxed()
.collect(Collectors.toMap(i -> i + 1, list::get));
答案 1 :(得分:6)
一种不需要随机访问源数据的干净解决方案是
Map<Integer,String> result = Stream.of("one", "two", "three")
.collect(HashMap::new, (m,s) -> m.put(m.size() + 1, s),
(m1,m2) -> {
int offset = m1.size();
m2.forEach((i,s) -> m1.put(i + offset, s));
});
这也适用于并行流。
在不太可能的情况下,这是重复执行的任务,值得将逻辑放入可重用的收集器中,包括一些优化:
public static <T> Collector<T,?,Map<Integer,T>> toIndexMap() {
return Collector.of(
HashMap::new,
(m,s) -> m.put(m.size() + 1, s),
(m1,m2) -> {
if(m1.isEmpty()) return m2;
if(!m2.isEmpty()) {
int offset = m1.size();
m2.forEach((i,s) -> m1.put(i + offset, s));
}
return m1;
});
}
然后可以像这样使用
Map<Integer,String> result = Stream.of("one", "two", "three")
.collect(MyCollectors.toIndexMap());
或
Map<Integer,Integer> result = IntStream.rangeClosed(1, 1000)
.boxed().parallel()
.collect(MyCollectors.toIndexMap());
答案 2 :(得分:5)
您的i
变量实际上不是最终变量。
您可以将AtomicInteger
用作Integer
包装器:
Stream<String> myStream = Arrays.asList("one","two","three").stream();
AtomicInteger atomicInteger = new AtomicInteger(0);
Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> atomicInteger.getAndIncrement(), Function.identity()));
我认为这有点棘手,因为它只能解决有效的最终变量问题。由于它是特殊的ThreadSafe版本,因此可能会带来一些开销。 answer by Samuel Philipp中的纯stream
解决方案可能更适合您的需求。
答案 3 :(得分:2)
尝试一下:
然后说String[] array = { "V","I","N","A","Y" };
,
Arrays.stream(array)
.map(ele-> index.getAndIncrement() + " -> " + ele)
.forEach(System.out::println);
输出:
0 -> V
1 -> I
2 -> N
3 -> A
4 -> Y
答案 4 :(得分:2)
Guava具有静态方法Streams#mapWithIndex
Stream<String> myStream = Stream.of("one","two","three");
Map<Long, String> result3 = Streams.mapWithIndex(myStream, (s, i) -> Maps.immutableEntry(i + 1, s))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// {1=one, 2=two, 3=three}
System.out.println(result3);
答案 5 :(得分:1)
在构造List.indexOf(Object o)
时,我们可以使用Map
方法获取列表中元素的索引:
List<String> list = Arrays.asList("one","two","three");
Map<Integer, String> result = list.stream()
.collect(Collectors.toMap(
k -> list.indexOf(k) + 1,
Function.identity(),
(v1, v2) -> v2));
System.out.println(result);
如果列表中有重复项,则元素的首次出现的索引将添加到最终映射中。同样,要解决键冲突期间地图中的合并错误,我们还需要确保toMap(keyMapper, valueMapper, mergeFunction)
输出:
{1=one, 2=two, 3=three}