«1. Обзор

Потоки в Java 8 не являются коллекциями, и доступ к элементам с помощью их индексов невозможен, но есть несколько приемов, позволяющих сделать это возможным.

В этой короткой статье мы рассмотрим, как перебирать Stream с помощью IntStream, StreamUtils, EntryStream и Vavr’s Stream.

2. Использование Plain Java

Мы можем перемещаться по потоку, используя диапазон целых чисел, а также извлечь выгоду из того факта, что исходные элементы находятся в массиве или коллекции, доступной по индексам.

Давайте реализуем метод, который перебирает индексы и демонстрирует этот подход.

Проще говоря, мы хотим получить массив строк и выбрать только четные индексированные элементы:

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

Еще один способ итерацию с индексами можно выполнить с помощью метода zipWithIndex() StreamUtils из библиотеки proton-pack (последнюю версию можно найти здесь).

Во-первых, вам нужно добавить его в ваш pom.xml:

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>protonpack</artifactId>
    <version>1.13</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

Мы также можем перебирать индексы, используя filterKeyValue() класса EntryStream из библиотеки StreamEx (последнюю версию можно найти здесь). Во-первых, нам нужно добавить его в наш pom.xml:

<dependency>
    <groupId>one.util</groupId>
    <artifactId>streamex</artifactId>
    <version>0.6.5</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. Итерация с использованием потока Vavre

Другой вероятный способ итерации — использование метода zipWithIndex() реализации Stream Vavr (ранее известного как Javaslang):

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. Заключение

В этом кратком руководстве мы рассмотрели четыре подхода к перебору потоков с использованием индексов. Потоки привлекли много внимания, и возможность перебирать их с помощью индексов может быть полезной.

В Java 8 Streams включено множество функций, некоторые из которых уже описаны в Baeldung.

Код для всех описанных здесь примеров и многое другое можно найти на GitHub.