Java 8 forEach с индексом

Есть ли способ создать метод forEach в Java 8, который выполняет итерацию с индексом? В идеале я хотел бы что-то вроде этого:

params.forEach((idx, e) -> query.bind(idx, e));

Лучшее, что я мог сделать прямо сейчас, это:

int idx = 0;
params.forEach(e -> {
  query.bind(idx, e);
  idx++;
});

person Josh Stone    schedule 01.04.2014    source источник
comment
на одну строку короче, если вы объедините приращение query.bind(idx++, e);, но это все, о чем я могу думать   -  person zapl    schedule 01.04.2014
comment
Кроме того, вы не должны иметь возможность изменять idx в этой лямбде.   -  person Sotirios Delimanolis    schedule 01.04.2014
comment
@SotiriosDelimanolis на самом деле компилируется   -  person Josh Stone    schedule 01.04.2014
comment
Это было бы, если бы idx была переменной экземпляра или чем-то в этом роде. Этого не будет, если код, который вы разместили, находится в теле метода/конструктора.   -  person Sotirios Delimanolis    schedule 01.04.2014
comment
@assylias Я голосую за повторное открытие этого вопроса, потому что не думаю, что это точная копия связанного вопроса. Автор связанного вопроса хотел получить доступ к индексу в середине обработки потока, в то время как основное внимание в этом вопросе уделяется простому получению индекса в (терминальном) методе forEach (в основном для замены традиционного цикла for, в котором индекс обрабатывается вручную). Я думаю, что мы не должны препятствовать тому, чтобы здесь было добавлено больше ответов. На самом деле я хотел бы внести свой вклад в ответ, который подходит для этого вопроса, но не для связанного вопроса.   -  person Dragan Bozanovic    schedule 18.02.2016
comment
Я тоже думаю, что другой ответ отличается, поскольку автору нужен был индекс для фильтрации. Таким образом, я проголосовал за повторное открытие вопроса. Вот как я бы реализовал forEachWithIndex и использовал его: forEachWithIndex   -  person Matthias Braun    schedule 02.05.2016


Ответы (3)


Поскольку вы перебираете индексируемую коллекцию (списки и т. д.), я предполагаю, что вы можете просто перебирать индексы элементов:

IntStream.range(0, params.size())
  .forEach(idx ->
    query.bind(
      idx,
      params.get(idx)
    )
  )
;

Полученный код похож на итерацию списка с помощью классического цикла for в стиле i++, за исключением более легкой параллелизации (при условии, конечно, что параллельный доступ только для чтения к параметрам безопасен).

person srborlongan    schedule 01.04.2014
comment
Будет ли это работать, если вам нужен определенный порядок параметров (например, отсортированный)? - person Tomer Cagan; 04.06.2016
comment
@TomerCagan Вы можете: сопоставить индексы с предпочтительным порядком, если указанный порядок может быть выражен как функция (чтобы перебрать параметры в обратном порядке, используйте IntStream.range(0, params.size()).map(i -> params.size() - 1 - i)) или просто укажите свой собственный диапазон, если конкретный порядок не может быть математически получен из результата IntStream.range(0, params.size()), например Stream. iterate(new int[] {0, 1}, ia -> new int[] {ia[1], ia[0] + ia[1]}).mapToInt(ia -> ia[0]).filter( i -> i ‹ params.size()).limit(params.size()) если вы хотите перебрать подмножество параметров в порядке Фибоначчи. - person srborlongan; 04.06.2016
comment
единственное соображение заключается в том, что при повторении LinkedList таким образом у вас будет O (nˆ2) вместо O (n) - person Bauna; 19.05.2017
comment
Еще раз, Java и ее ужасный синтаксис для простых вещей. - person AFP_555; 04.11.2017
comment
Когда java предложит более простую альтернативу? Интересно - person Costi Muraru; 04.11.2018
comment
этот ответ быстрее, чем int[] idx = { 0 }; params.forEach(e -> query.bind(idx[0]++, e)); ? - person ; 28.02.2019
comment
Вы можете использовать другие параметры вокруг AtomicInteger index = new AtomicInteger(); params.forEach(e -> query.bind(index.incrementAndGet(), e)); - person sopheamak; 22.11.2019

Он работает с params, если вы захватываете массив с одним элементом, который содержит текущий индекс.

int[] idx = { 0 };
params.forEach(e -> query.bind(idx[0]++, e));

Приведенный выше код предполагает, что метод forEach перебирает элементы в порядке их появления. Интерфейс Iterable определяет это поведение для всех классов, если не указано иное. Очевидно, это работает для всех реализаций Iterable из стандартной библиотеки, и изменение этого поведения в будущем нарушит обратную совместимость.

Если вы работаете с Streams вместо Collections/Iterables, вам следует использовать forEachOrdered, поскольку forEach могут выполняться одновременно, а элементы могут располагаться в другом порядке. Следующий код работает как для последовательных, так и для параллельных потоков:

int[] idx = { 0 };
params.stream().forEachOrdered(e -> query.bind(idx[0]++, e));
person nosid    schedule 01.04.2014
comment
Вместо массива int[] лучше использовать AtomicInteger. Это также гарантирует, что если элементы встречаются не по порядку, мы, по крайней мере, получим уникальный индекс для каждого элемента. - person Brett Ryan; 29.09.2015
comment
@BrettRyan: я не согласен. AtomicInteger выражает неверное намерение, оно менее эффективно, и в этом случае гарантируется последовательное выполнение. - person nosid; 29.09.2015
comment
Разве MutableInt в библиотеке apache lang не работает? Это не обязательно должна быть синхронизированная структура при использовании forEachOrdered, я прав? - person Skychan; 16.10.2015
comment
@Skychan: MutableInt в этой ситуации не помогает, потому что в нем отсутствуют некоторые операции. В частности, что-то вроде getAndIncrement. - person nosid; 18.10.2015
comment
@nosid Возможно, ты прав. У меня сложилось впечатление, что при вызове forEachOrdered (или forEach в списке) нам гарантируется порядок, в котором элементы зацикливаются. Не означает ли это, что для операции не требуется синхронизация? Можно просто использовать MutableInt.increment(). Я ошибся? - person Skychan; 22.10.2015
comment
@Skychan: Ты прав, синхронизация не нужна. Однако MutableInt работает плохо из-за сигнатур его методов (т. е. increment не возвращает значения). - person nosid; 23.10.2015
comment
@nosid Я не думаю, что использование int[] раскрывает больше намерений, чем AtomicInteger. А если важна производительность, то, возможно, лучше использовать цикл for, поскольку он позволяет избежать механизма Stream, который, вероятно, затмевает высокооптимизированные классы Atomic*. - person btiernay; 05.11.2016
comment
будет ли он работать с параллельными потоками? (возможно нет) - person Legna; 21.09.2017

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

int idx = 0;
for (Param p : params) query.bind(idx++, p);

Или старый стиль:

for (int idx = 0; idx < params.size(); idx++) query.bind(idx, params.get(idx));
person assylias    schedule 01.04.2014
comment
Поздно, но полностью согласен. Обычного старого цикла for достаточно, когда требуется индекс. - person Vortex; 08.10.2017
comment
@Vortex И более читабельно. - person AFP_555; 04.11.2017