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

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

有更简洁的方式吗?

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


当前回答

正如jean-baptiste-yunès所说,如果您的流是基于java列表的,那么使用AtomicInteger及其incrementAndGet方法是一个很好的解决问题的方法,并且返回的整数与原始列表中的索引对应,只要您不使用并行流。

其他回答

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

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

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

如果您需要forEach中的索引,那么这提供了一种方法。

  public class IndexedValue {

    private final int    index;
    private final Object value;

    public IndexedValue(final int index, final Object value) { 
        this.index = index;
        this.value = value;
    }

    public int getIndex() {
        return index;
    }

    public Object getValue() {
        return value;
    }
}

然后像下面这样使用它。

@Test
public void withIndex() {
    final List<String> list = Arrays.asList("a", "b");
    IntStream.range(0, list.size())
             .mapToObj(index -> new IndexedValue(index, list.get(index)))
             .forEach(indexValue -> {
                 System.out.println(String.format("%d, %s",
                                                  indexValue.getIndex(),
                                                  indexValue.getValue().toString()));
             });
}

我在这里找到了解决方案,当流创建的列表或数组(你知道的大小)。但是如果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());
    });
}

这里是常用的算盘代码

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

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

下面是标准Java的解决方案:

在线解决方案:

Arrays.stream("zero,one,two,three,four".split(","))
        .map(new Function<String, Map.Entry<Integer, String>>() {
            int index;

            @Override
            public Map.Entry<Integer, String> apply(String s) {
                return Map.entry(index++, s);
            }
        })
        .forEach(System.out::println);

更可读的解决方案与实用方法:

static <T> Function<T, Map.Entry<Integer, T>> mapWithIntIndex() {
    return new Function<T, Map.Entry<Integer, T>>() {
        int index;

        @Override
        public Map.Entry<Integer, T> apply(T t) {
            return Map.entry(index++, t);
        }
    };
}

...
Arrays.stream("zero,one,two,three,four".split(","))
        .map(mapWithIntIndex())
        .forEach(System.out::println);