206

Is there a way to build a forEach method in Java 8 that iterates with an index? Ideally I'd like something like this:

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

The best I could do right now is:

int idx = 0;
params.forEach(e -> {
  query.bind(idx, e);
  idx++;
});
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
Josh Stone
  • 4,328
  • 7
  • 29
  • 37
  • 2
    one line shorter if you merge the increment `query.bind(idx++, e);` but that's all I can think of – zapl Apr 01 '14 at 17:42
  • 19
    Also, you shouldn't be able to modify `idx` within that lambda. – Sotirios Delimanolis Apr 01 '14 at 17:49
  • 1
    @SotiriosDelimanolis it actually compiles – Josh Stone Apr 01 '14 at 17:58
  • It would if `idx` is an instance variable or something. It will not if the code you posted is in a method/constructor body. – Sotirios Delimanolis Apr 01 '14 at 17:59
  • 3
    @assylias I am voting to reopen this question because I don't think it is an exact duplicate of the linked question. The poster of the linked question wanted to get access to the index in the middle of stream processing, while the focus of this question is just to get the index in the (terminal) `forEach` method (basically to replace the traditional for loop in which index is manipulated manually). I think that we should not prevent more answers to be added here. Actually I would like to contribute with an answer which is suitable to this question, but not to the linked question. – Dragan Bozanovic Feb 18 '16 at 17:14
  • I, too, think the [other answer](https://stackoverflow.com/questions/18552005/is-there-a-concise-way-to-iterate-over-a-stream-with-indices-in-java-8) is different since the poster needed the index for filtering. Thus I voted to reopen the question. Here is how I would implement `forEachWithIndex` and use it: [`forEachWithIndex`](http://pastebin.com/wNCJbfPq) – Matthias Braun May 02 '16 at 18:00

3 Answers3

229

Since you are iterating over an indexable collection (lists, etc.), I presume that you can then just iterate with the indices of the elements:

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

The resulting code is similar to iterating a list with the classic i++-style for loop, except with easier parallelizability (assuming, of course, that concurrent read-only access to params is safe).

srborlongan
  • 4,460
  • 4
  • 26
  • 33
  • Would this work in case you want specific order for params (e.g. sorted)? – Tomer Cagan Jun 04 '16 at 11:49
  • 1
    @TomerCagan You may either: map the indices to the preferred order, if said order can be expressed as a function (to iterate the params in reverse, use IntStream.range(0, params.size()).map(i -> params.size() - 1 - i)), or just provide your own range if the specific order is not something that can be mathematically derived from the result of IntStream.range(0, params.size()), like 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()) if you want to iterate a subset of the params in Fibonacci order. – srborlongan Jun 04 '16 at 12:27
  • 4
    the only consideration is that when you are iterating a LinkedList in this way you are going to have O(nˆ2) instead of O(n) – Bauna May 19 '17 at 13:26
  • 1
    When will java offer a simpler alternative.. I wonder – Costi Muraru Nov 04 '18 at 13:07
  • 2
    is this answer more fast than `int[] idx = { 0 }; params.forEach(e -> query.bind(idx[0]++, e));` ? –  Feb 28 '19 at 07:01
  • 1
    You can do other options around AtomicInteger index = new AtomicInteger(); params.forEach(e -> query.bind(index.incrementAndGet(), e)); – sopheamak Nov 22 '19 at 01:16
70

It works with params if you capture an array with one element, that holds the current index.

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

The above code assumes, that the method forEach iterates through the elements in encounter order. The interface Iterable specifies this behaviour for all classes unless otherwise documented. Apparently it works for all implementations of Iterable from the standard library, and changing this behaviour in the future would break backward-compatibility.

If you are working with Streams instead of Collections/Iterables, you should use forEachOrdered, because forEach can be executed concurrently and the elements can occur in different order. The following code works for both sequential and parallel streams:

int[] idx = { 0 };
params.stream().forEachOrdered(e -> query.bind(idx[0]++, e));
nosid
  • 48,932
  • 13
  • 112
  • 139
  • 7
    Rather than using an `int[]` array it's best to use an [AtomicInteger](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html). This will also ensure that if the elements are encountered out of order we at least get a unique index for each element. – Brett Ryan Sep 29 '15 at 00:46
  • 1
    @BrettRyan: I do not agree. `AtomicInteger` expresses the wrong intent, it is less efficient, and the _sequential execution_ is guaranteed in this case. – nosid Sep 29 '15 at 18:18
  • Wouldn't MutableInt in apache lang library work well? It doesn't need to be a synchronized structure if using forEachOrdered, am I right? – Skystrider Oct 16 '15 at 15:24
  • @Skychan: [MutableInt](https://commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/apache/commons/lang3/mutable/MutableInt.html) doesn't help in this situation because it lacks some operations. In particular something like [getAndIncrement](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html#getAndIncrement--). – nosid Oct 18 '15 at 11:07
  • @nosid You may be correct. It was my impression that when calling forEachOrdered (or forEach on a list) then we are guaranteed the order on which the items are looped over. Wouldn't this mean that there is no synchronization required on the operation? Can simply use MutableInt.increment(). Am I wrong? – Skystrider Oct 22 '15 at 15:19
  • @Skychan: You are right, no need for synchronization. However, `MutableInt` doesn't work well because of its method signatures (i.e. `increment` returns no value). – nosid Oct 23 '15 at 15:49
  • 5
    @nosid I don't think using an `int[]` is more intent revealing than `AtomicInteger`. And if performance is a concern, perhaps a for loop is better as it avoids the `Stream` machinery which probably dwarfs the highly optimized `Atomic*` classes. – btiernay Nov 05 '16 at 14:07
  • would it work with parallel streams? (probably not) – Legna Sep 21 '17 at 17:51
59

There are workarounds but no clean/short/sweet way to do it with streams and to be honest, you would probably be better off with:

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

Or the older style:

for (int idx = 0; idx < params.size(); idx++) query.bind(idx, params.get(idx));
bernard paulus
  • 1,644
  • 1
  • 21
  • 33
assylias
  • 321,522
  • 82
  • 660
  • 783