1. 概述
Java Stream不是集合,无法通过索引直接访问元素。但仍有几种技巧可以实现带索引的遍历。
本文将介绍四种主流方案:使用IntStream
、StreamUtils
、EntryStream
和Vavr的Stream
实现带索引遍历。
2. 使用原生Java
通过IntStream
创建索引范围,结合原始数组/集合的索引访问能力,可实现带索引遍历。
以下示例从字符串数组中筛选偶数索引元素:
public List<String> getEvenIndexedStrings(String[] names) {
List<String> evenIndexedNames = IntStream
.range(0, names.length)
.filter(i -> i % 2 == 0)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
return evenIndexedNames;
}
测试验证:
@Test
public void whenCalled_thenReturnListOfEvenIndexedStrings() {
String[] names
= {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"};
List<String> expectedResult
= Arrays.asList("Afrim", "Besim", "Durim");
List<String> actualResult
= StreamIndices.getEvenIndexedStrings(names);
assertEquals(expectedResult, actualResult);
}
3. 使用StreamUtils
通过proton-pack
库的StreamUtils.zipWithIndex()
方法实现(最新版本)。
先添加依赖:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>protonpack</artifactId>
<version>1.16</version>
</dependency>
实现代码:
public List<Indexed<String>> getEvenIndexedStrings(List<String> names) {
List<Indexed<String>> list = StreamUtils
.zipWithIndex(names.stream())
.filter(i -> i.getIndex() % 2 == 0)
.collect(Collectors.toList());
return list;
}
测试用例:
@Test
public void whenCalled_thenReturnListOfEvenIndexedStrings() {
List<String> names = Arrays.asList(
"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim");
List<Indexed<String>> expectedResult = Arrays.asList(
Indexed.index(0, "Afrim"),
Indexed.index(2, "Besim"),
Indexed.index(4, "Durim"));
List<Indexed<String>> actualResult
= StreamIndices.getEvenIndexedStrings(names);
assertEquals(expectedResult, actualResult);
}
4. 使用StreamEx
利用StreamEx
库的EntryStream.filterKeyValue()
实现(最新版本)。
添加依赖:
<dependency>
<groupId>one.util</groupId>
<artifactId>streamex</artifactId>
<version>0.8.1</version>
</dependency>
实现示例:
public List<String> getEvenIndexedStringsVersionTwo(List<String> names) {
return EntryStream.of(names)
.filterKeyValue((index, name) -> index % 2 == 0)
.values()
.toList();
}
测试验证:
@Test
public void whenCalled_thenReturnListOfEvenIndexedStringsVersionTwo() {
String[] names
= {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"};
List<String> expectedResult
= Arrays.asList("Afrim", "Besim", "Durim");
List<String> actualResult
= StreamIndices.getEvenIndexedStrings(names);
assertEquals(expectedResult, actualResult);
}
5. 使用Vavr的Stream
通过Vavr(原Javaslang)的Stream.zipWithIndex()
方法实现。
添加依赖:
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.4</version>
</dependency>
实现代码:
public List<String> getOddIndexedStringsVersionTwo(String[] names) {
return Stream
.of(names)
.zipWithIndex()
.filter(tuple -> tuple._2 % 2 == 1)
.map(tuple -> tuple._1)
.toJavaList();
}
测试用例:
@Test
public void whenCalled_thenReturnListOfOddStringsVersionTwo() {
String[] names
= {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"};
List<String> expectedResult
= Arrays.asList("Bashkim", "Lulzim", "Shpetim");
List<String> actualResult
= StreamIndices.getOddIndexedStringsVersionTwo(names);
assertEquals(expectedResult, actualResult);
}
✅ 想深入了解Vavr?可参考这篇文章。
6. 使用AtomicInteger
通过AtomicInteger
维护可变计数器,在lambda表达式中跟踪索引。仅适用于顺序流:
public List<String> getEvenIndexedStringsUsingAtomicInteger(String[] names) {
AtomicInteger index = new AtomicInteger(0);
return Arrays.stream(names)
.filter(name -> index.getAndIncrement() % 2 == 0)
.collect(Collectors.toList());
}
顺序流测试:
@Test
public void whenCalledSequentially_thenReturnListOfEvenIndexedStrings() {
String[] names = {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"};
List<String> expectedResult = Arrays.asList("Afrim", "Besim", "Durim");
List<String> actualResult = StreamIndices.getEvenIndexedStringsUsingAtomicInteger(names);
assertEquals(expectedResult, actualResult);
}
⚠️ 并行流踩坑警告:AtomicInteger
在并行流中会导致竞态条件!元素处理顺序不可预测,索引分配会错乱:
public List<String> getEvenIndexedStringsAtomicIntegerParallel(String[] names) {
AtomicInteger index = new AtomicInteger(0);
return Arrays.stream(names)
.parallel() // 切换为并行流
.filter(name -> index.getAndIncrement() % 2 == 0)
.collect(Collectors.toList());
}
并行流测试(预期失败):
@Test
public void whenCalledInParallel_thenResultInconsistent() {
String[] names = {"Afrim", "Bashkim", "Besim", "Lulzim", "Durim", "Shpetim"};
List<String> result = StreamIndices.getEvenIndexedStringsAtomicIntegerParallel(names);
// 并行模式下可能间歇性失败
assertNotEquals(Arrays.asList("Afrim", "Besim", "Durim"), result);
}
💡 并行流解决方案:改用
IntStream.range()
避免竞态条件。
7. 总结
本文介绍了四种带索引遍历Stream的方案:
- 原生Java +
IntStream
StreamUtils.zipWithIndex()
EntryStream.filterKeyValue()
- Vavr的
Stream.zipWithIndex()
选择建议:
- ✅ 简单场景:原生方案足够
- ✅ 需要索引对象:选
StreamUtils
或Vavr - ❌ 避免在并行流中使用
AtomicInteger
Stream作为Java 8的核心特性,掌握这些技巧能显著提升处理效率。更多Stream特性可参考Baeldung。