本文翻译自:Is there a concise way to iterate over a stream with indices in Java 8?
Is there a concise way to iterate over a stream whilst having access to the index in the stream? 在访问流中的索引时,是否有一种简洁的方法可以在流上进行迭代?
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
Stream<Integer> indices = intRange(1, names.length).boxed();
nameList = zip(indices, stream(names), SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey())
.map(Entry::getValue)
.collect(toList());
which seems rather disappointing compared to the LINQ example given there 与那里给出的LINQ示例相比,这似乎令人失望
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
Is there a more concise way? 有没有更简洁的方法?
Further it seems the zip has either moved or been removed... 此外,似乎拉链已移动或已被移除...
#1楼
参考:https://stackoom.com/question/1FqDt/在Java-中-有没有一种简洁的方法可以迭代带有索引的流
#2楼
There isn't a way to iterate over a Stream
whilst having access to the index because a Stream
is unlike any Collection
. 没有一种方法可以在访问索引的同时对Stream
进行迭代,因为Stream
与任何Collection
不同。 A Stream
is merely a pipeline for carrying data from one place to another, as stated in the documentation : 如文档中所述, Stream
仅仅是将数据从一个地方传送到另一个地方的管道:
No storage. 没有存储空间。 A stream is not a data structure that stores elements; 流不是存储元素的数据结构。 instead, they carry values from a source (which could be a data structure, a generator, an IO channel, etc) through a pipeline of computational operations. 取而代之的是,它们通过一系列计算操作从源(可能是数据结构,生成器,IO通道等)中携带值。
Of course, as you appear to be hinting at in your question, you could always convert your Stream<V>
to a Collection<V>
, such as a List<V>
, in which you will have access to the indexes. 当然,正如您在问题中所暗示的那样,您始终可以将Stream<V>
转换为Collection<V>
(例如List<V>
,在其中可以访问索引。
#3楼
The cleanest way is to start from a stream of indices: 最干净的方法是从索引流开始:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
The resulting list contains "Erik" only. 结果列表仅包含“ Erik”。
One alternative which looks more familiar when you are used to for loops would be to maintain an ad hoc counter using a mutable object, for example an AtomicInteger
: 当您习惯于循环时,一种看起来更熟悉的替代方法是使用可变对象(例如AtomicInteger
维护临时计数器:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
AtomicInteger index = new AtomicInteger();
List<String> list = Arrays.stream(names)
.filter(n -> n.length() <= index.incrementAndGet())
.collect(Collectors.toList());
Note that using the latter method on a parallel stream could break as the items would not necesarily be processed "in order" . 注意, 在并行流上使用后一种方法可能会中断,因为不必“按顺序”处理项目 。
#4楼
The Java 8 streams API lacks the features of getting the index of a stream element as well as the ability to zip streams together. Java 8流API缺乏获取流元素的索引以及将流压缩在一起的功能。 This is unfortunate, as it makes certain applications (like the LINQ challenges) more difficult than they would be otherwise. 这是不幸的,因为它使某些应用程序(如LINQ挑战)比其他情况下更加困难。
There are often workarounds, however. 但是,通常有解决方法。 Usually this can be done by "driving" the stream with an integer range, and taking advantage of the fact that the original elements are often in an array or in a collection accessible by index. 通常,这可以通过“驱动”具有整数范围的流并利用原始元素通常位于数组或可通过索引访问的集合中这一事实来实现。 For example, the Challenge 2 problem can be solved this way: 例如,可以通过以下方式解决“挑战2”问题:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList =
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(toList());
As I mentioned above, this takes advantage of the fact that the data source (the names array) is directly indexable. 如前所述,这利用了数据源(名称数组)可直接索引的事实。 If it weren't, this technique wouldn't work. 如果不是,则此技术将不起作用。
I'll admit that this doesn't satisfy the intent of Challenge 2. Nonetheless it does solve the problem reasonably effectively. 我承认这不能满足挑战2的意图。尽管如此,它确实可以有效地解决问题。
EDIT 编辑
My previous code example used flatMap
to fuse the filter and map operations, but this was cumbersome and provided no advantage. 我以前的代码示例使用flatMap
融合了filter和map操作,但这很麻烦并且没有任何优势。 I've updated the example per the comment from Holger. 根据Holger的评论,我已经更新了示例。
#5楼
I've used the following solution in my project. 我在项目中使用了以下解决方案。 I think it is better than using mutable objects or integer ranges. 我认为这比使用可变对象或整数范围更好。
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;
public class CollectionUtils {
private CollectionUtils() { }
/**
* Converts an {@link java.util.Iterator} to {@link java.util.stream.Stream}.
*/
public static <T> Stream<T> iterate(Iterator<? extends T> iterator) {
int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE;
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false);
}
/**
* Zips the specified stream with its indices.
*/
public static <T> Stream<Map.Entry<Integer, T>> zipWithIndex(Stream<? extends T> stream) {
return iterate(new Iterator<Map.Entry<Integer, T>>() {
private final Iterator<? extends T> streamIterator = stream.iterator();
private int index = 0;
@Override
public boolean hasNext() {
return streamIterator.hasNext();
}
@Override
public Map.Entry<Integer, T> next() {
return new AbstractMap.SimpleImmutableEntry<>(index++, streamIterator.next());
}
});
}
/**
* Returns a stream consisting of the results of applying the given two-arguments function to the elements of this stream.
* The first argument of the function is the element index and the second one - the element value.
*/
public static <T, R> Stream<R> mapWithIndex(Stream<? extends T> stream, BiFunction<Integer, ? super T, ? extends R> mapper) {
return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue()));
}
public static void main(String[] args) {
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
System.out.println("Test zipWithIndex");
zipWithIndex(Arrays.stream(names)).forEach(entry -> System.out.println(entry));
System.out.println();
System.out.println("Test mapWithIndex");
mapWithIndex(Arrays.stream(names), (Integer index, String name) -> index+"="+name).forEach((String s) -> System.out.println(s));
}
}
#6楼
With https://github.com/poetix/protonpack u can do that zip: 使用https://github.com/poetix/protonpack,您可以执行以下zip:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
Stream<Integer> indices = IntStream.range(0, names.length).boxed();
nameList = StreamUtils.zip(indices, stream(names),SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey()).map(Entry::getValue).collect(toList());
System.out.println(nameList);