是否有一种简洁的方法在流上迭代,同时访问流中的索引?

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

与这里给出的LINQ示例相比,这似乎相当令人失望

string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();

有更简洁的方式吗?

此外,似乎拉链已经移动或被拆除…


当前回答

我在这里找到了解决方案,当流创建的列表或数组(你知道的大小)。但是如果Stream的大小未知呢?在这种情况下,试试这个变体:

public class WithIndex<T> {
    private int index;
    private T value;

    WithIndex(int index, T value) {
        this.index = index;
        this.value = value;
    }

    public int index() {
        return index;
    }

    public T value() {
        return value;
    }

    @Override
    public String toString() {
        return value + "(" + index + ")";
    }

    public static <T> Function<T, WithIndex<T>> indexed() {
        return new Function<T, WithIndex<T>>() {
            int index = 0;
            @Override
            public WithIndex<T> apply(T t) {
                return new WithIndex<>(index++, t);
            }
        };
    }
}

用法:

public static void main(String[] args) {
    Stream<String> stream = Stream.of("a", "b", "c", "d", "e");
    stream.map(WithIndex.indexed()).forEachOrdered(e -> {
        System.out.println(e.index() + " -> " + e.value());
    });
}

其他回答

如果你碰巧使用Vavr(以前称为Javaslang),你可以利用专用的方法:

Stream.of("A", "B", "C")
  .zipWithIndex();

如果我们打印出内容,我们会看到一些有趣的东西:

Stream((A, 0), ?)

这是因为流是懒惰的,我们不知道流中的下一项。

没有一种方法可以在访问索引的同时遍历流,因为流不同于任何集合。Stream只是一个将数据从一个地方传输到另一个地方的管道,如文档中所述:

没有存储。流不是存储元素的数据结构;相反,它们通过计算操作管道携带来自源(可以是数据结构、生成器、IO通道等)的值。

当然,正如您在问题中暗示的那样,您总是可以将Stream<V>转换为Collection<V>,例如List<V>,在其中您将可以访问索引。

最简洁的方法是从一系列指数开始:

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

结果列表只包含“Erik”。


当你习惯for循环时,另一种看起来更熟悉的方法是使用可变对象维护一个临时计数器,例如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());

注意,在并行流上使用后一种方法可能会中断,因为项目不一定会“按顺序”处理。

这里是常用的算盘代码

Stream.of(names).indexed()
      .filter(e -> e.value().length() <= e.index())
      .map(Indexed::value).toList();

披露:我是abacus-common的开发者。

自番石榴21起,就可以使用了

Streams.mapWithIndex()

示例(来自官方文档):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")