diff --git a/android/guava/src/com/google/common/collect/Streams.java b/android/guava/src/com/google/common/collect/Streams.java
index 22b358955904..be436a290433 100644
--- a/android/guava/src/com/google/common/collect/Streams.java
+++ b/android/guava/src/com/google/common/collect/Streams.java
@@ -44,6 +44,7 @@
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
import java.util.function.LongConsumer;
+import java.util.function.ObjLongConsumer;
import java.util.stream.BaseStream;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
@@ -414,6 +415,48 @@ public boolean tryAdvance(Consumer super R> action) {
}
}
+ /**
+ * Invokes {@code consumer} once for each element of {@code stream} and its index in the stream.
+ * For example,
+ *
+ * {@snippet :
+ * Streams.forEachWithIndex(
+ * Stream.of("a", "b", "c"),
+ * (e, index) -> System.out.println(index + ": " + e))
+ * }
+ *
+ *
would print:
+ *
+ * {@snippet :
+ * 0: a
+ * 1: b
+ * 2: c
+ * }
+ *
+ *
Warning: If {@code stream} is a parallel stream, the elements may be passed to the
+ * consumer in any order. The index assigned to each element reflects its position in the stream's
+ * encounter order. If the stream has no defined encounter order, the index assigned to
+ * each element is arbitrary.
+ *
+ *
This method behaves equivalently to applying {@link #mapWithIndex(Stream,
+ * FunctionWithIndex)} and then invoking {@link Stream#forEach} on the resulting stream.
+ *
+ * @since 33.6.0
+ */
+ @Beta
+ public static void forEachWithIndex(
+ Stream stream, ObjLongConsumer super T> consumer) {
+ checkNotNull(stream);
+ checkNotNull(consumer);
+ mapWithIndex(
+ stream,
+ (element, index) -> {
+ consumer.accept(element, index);
+ return null;
+ })
+ .forEach(ignored -> {});
+ }
+
// Use this carefully - it doesn't implement value semantics
private static final class TemporaryPair {
@ParametricNullness final A a;
diff --git a/guava-tests/test/com/google/common/collect/StreamsTest.java b/guava-tests/test/com/google/common/collect/StreamsTest.java
index 2d72c5067f39..c20fa1aa82a6 100644
--- a/guava-tests/test/com/google/common/collect/StreamsTest.java
+++ b/guava-tests/test/com/google/common/collect/StreamsTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toCollection;
+import static org.junit.Assert.assertThrows;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
@@ -33,6 +34,7 @@
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.DoubleStream;
@@ -508,6 +510,66 @@ public void testForEachPair_finiteWithInfinite() {
assertThat(list).containsExactly("a:1", "b:2", "c:3");
}
+ private void testForEachWithIndex(Function, Stream> sourceFactory) {
+ List result = new ArrayList<>();
+ Streams.forEachWithIndex(
+ sourceFactory.apply(ImmutableList.of()), (e, i) -> result.add(i + ":" + e));
+ assertThat(result).isEmpty();
+
+ Streams.forEachWithIndex(
+ sourceFactory.apply(ImmutableList.of("a", "b", "c")),
+ (e, i) -> result.add(i + ":" + e));
+ assertThat(result).containsExactly("0:a", "1:b", "2:c").inOrder();
+ }
+
+ public void testForEachWithIndex_sizedSource() {
+ testForEachWithIndex(Collection::stream);
+ }
+
+ public void testForEachWithIndex_nullElement() {
+ List<@Nullable String> collected = new ArrayList<>();
+ List indices = new ArrayList<>();
+ Streams.forEachWithIndex(
+ Stream.<@Nullable String>of("a", null, "c"),
+ (e, i) -> {
+ collected.add(e);
+ indices.add(i);
+ });
+ assertThat(collected).containsExactly("a", null, "c").inOrder();
+ assertThat(indices).containsExactly(0L, 1L, 2L).inOrder();
+ }
+
+ public void testForEachWithIndex_nullChecks() {
+ assertThrows(
+ NullPointerException.class, () -> Streams.forEachWithIndex(null, (e, i) -> {}));
+ assertThrows(
+ NullPointerException.class, () -> Streams.forEachWithIndex(Stream.of("a"), null));
+ }
+
+ public void testForEachWithIndex_unsizedSource() {
+ // flatMap strips SUBSIZED, exercising mapWithIndex's iterator-backed fallback.
+ testForEachWithIndex(
+ elems ->
+ Stream.<@Nullable Object>of((Object) null)
+ .flatMap(unused -> ImmutableList.copyOf(elems).stream()));
+ }
+
+ public void testForEachWithIndex_parallelStream() {
+ int n = 200;
+ List input = new ArrayList<>();
+ for (int j = 0; j < n; j++) {
+ input.add("e" + j);
+ }
+
+ ConcurrentHashMap result = new ConcurrentHashMap<>();
+ Streams.forEachWithIndex(input.stream().parallel(), (e, i) -> result.put(e, i));
+
+ assertThat(result).hasSize(n);
+ for (int j = 0; j < n; j++) {
+ assertThat(result).containsEntry("e" + j, (long) j);
+ }
+ }
+
public void testForEachPair_parallel() {
Stream streamA = IntStream.range(0, 100000).mapToObj(String::valueOf).parallel();
Stream streamB = IntStream.range(0, 100000).mapToObj(i -> i).parallel();
diff --git a/guava/src/com/google/common/collect/Streams.java b/guava/src/com/google/common/collect/Streams.java
index 276233b9013b..ce0d197911d4 100644
--- a/guava/src/com/google/common/collect/Streams.java
+++ b/guava/src/com/google/common/collect/Streams.java
@@ -44,6 +44,7 @@
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
import java.util.function.LongConsumer;
+import java.util.function.ObjLongConsumer;
import java.util.stream.BaseStream;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
@@ -408,6 +409,48 @@ public boolean tryAdvance(Consumer super R> action) {
}
}
+ /**
+ * Invokes {@code consumer} once for each element of {@code stream} and its index in the stream.
+ * For example,
+ *
+ * {@snippet :
+ * Streams.forEachWithIndex(
+ * Stream.of("a", "b", "c"),
+ * (e, index) -> System.out.println(index + ": " + e))
+ * }
+ *
+ * would print:
+ *
+ * {@snippet :
+ * 0: a
+ * 1: b
+ * 2: c
+ * }
+ *
+ *
Warning: If {@code stream} is a parallel stream, the elements may be passed to the
+ * consumer in any order. The index assigned to each element reflects its position in the stream's
+ * encounter order. If the stream has no defined encounter order, the index assigned to
+ * each element is arbitrary.
+ *
+ *
This method behaves equivalently to applying {@link #mapWithIndex(Stream,
+ * FunctionWithIndex)} and then invoking {@link Stream#forEach} on the resulting stream.
+ *
+ * @since 33.6.0
+ */
+ @Beta
+ public static void forEachWithIndex(
+ Stream stream, ObjLongConsumer super T> consumer) {
+ checkNotNull(stream);
+ checkNotNull(consumer);
+ mapWithIndex(
+ stream,
+ (element, index) -> {
+ consumer.accept(element, index);
+ return null;
+ })
+ .forEach(ignored -> {});
+ }
+
// Use this carefully - it doesn't implement value semantics
private static final class TemporaryPair {
@ParametricNullness final A a;