I need an index with this List iteration method
Iterating over Lists in Java with indices using external and internal iterators
How to iterate over a List in Java with indices
In Java, there are a few ways to to iterate over a List
with indices. I will cover a few of the most common external iteration approaches, and how they can be combined with providing an index. I will explain how to use the RandomAccess
interface to safely iterate over the List
interface.
Iterate using an Iterator
This is the most basic form of collection iteration that works with any Collection
(prior to Java 5), and any Iterable
type in Java (since Java 5).
List<Integer> list = Lists.mutable.with(1, 2, 3);
int index = 0;
for (Iterator<Integer> it = list.iterator(); it.hasNext(); index++)
{
Integer integer = it.next();
System.out.println(integer + ":" + index);
}
// Outputs:
// 1:0
// 2:1
// 3:2
Use this form of iteration if you need to directly manipulate the Iterator
. An example of this would be if you had a flattened list of pairs which you needed to call next
() twice to re-construct their pairs.
Iterator using the Java 5 for-each loop
The for-each loop is just syntactic sugar for a for-loop with an Iterator
.
List<Integer> list = List.of(1, 2, 3);
int index = 0;
for (Integer integer : list)
{
System.out.println(integer + ":" + index++);
}
// Outputs:
// 1:0
// 2:1
// 3:2
Use this form of iteration in most cases, except when are concerned with raw iteration performance and creating an Iterator
instance.
Iterate using an indexed for-loop
This is the fastest external iteration for-loop for a random access List
.
List<Integer> list = Lists.mutable.with(1, 2, 3);
for (int i = 0; i < list.size(); i++)
{
Integer integer = list.get(i);
System.out.println(integer + ":" + i);
}
// Outputs:
// 1:0
// 2:1
// 3:2
Use this form of iteration when you are optimizing iteration code for performance. This is mostly applicable in library or framework code, but may also apply to some performance critical or garbage sensitive areas in application code. Only use this code in places where you can guarantee that a List
is RandomAccess
.
But how do you know if a List supports random access?
RandomAccess Interface
In Java 1.4, a marker interface was introduced named RandomAccess
. I don’t know why it was added only as a marker interface, and didn’t have methods for size
and get
defined on it. This interface is used by many library algorithms to optimize for RandomAccess
Lists, and to provide performance safe iteration for non-RandomAccess
List
instances. You will find references to this interface in instanceof
checks in the standard Java library (look in the Collections
class for some examples). The following is an example of using the RandomAccess
interface to iterate over a List
that may or may not be RandomAccess
.
List<Integer> list = Stream.of(1, 2, 3)
.collect(Collectors.toCollection(LinkedList::new));
if (list instanceof RandomAccess)
{
for (int i = 0; i < list.size(); i++)
{
Integer integer = list.get(i);
System.out.println(integer + ":" + i);
}
}
else
{
int index = 0;
for (Integer integer : list)
{
System.out.println(integer + ":" + index++);
}
}
// Outputs:
// 1:0
// 2:1
// 3:2
In this example, the else
part of the if
statement will execute, since LinkedList
is not a RandomAccess
List
.
IntStream.range() and subList()
Since Java 8, it is possible to use IntStream.range()
to iterate over a set of indices and use List
look ups using get()
. IntStream.range()
can be used an object-oriented version of an indexed for-loop.
List<Integer> list = List.of(1, 2, 3, 4, 5);
IntStream.range(1, 4)
.forEach(index ->
System.out.println(list.get(index) + ":" + index));
// Outputs:
// 2:1
// 3:2
// 4:3
With this approach, a developer can iterate over a specific range of indexes in the List
. The same can be accomplished using subList
.
List<Integer> list = List.of(1, 2, 3, 4, 5);
list.subList(1, 4).forEach(System.out::println);
// Outputs:
// 2
// 3
// 4
This code will output all but the first and last element. Both InStream.range()
and subList()
are inclusive for the from index, and exclusive for the to index.
Notice however, that with subList()
, we lost access to the index in the call to forEach
. There is no way to get access to this index back using subList
.
Internal Eclipse Collections iterators with an index
Eclipse Collections has a few internal iterators which are passed the element and index to the functional interface that is provided.
forEachWithIndex (OrderedIterable)
This method will iterate from first element to last element passing each element and its index to the ObjectIntProcedure
provided.
MutableList<Integer> list = Lists.mutable.with(1, 2, 3);
list.forEachWithIndex((each, index) ->
System.out.println(each + ": " + index));
// Outputs:
// 1: 0
// 2: 1
// 3: 2
reverseForEachWithIndex (ReversibleIterable)
This method will iterate from last element to first element passing each element and its index to the ObjectIntProcedure
provided.
MutableList<Integer> list = Lists.mutable.with(1, 2, 3);
list.reverseForEachWithIndex((each, index) ->
System.out.println(each + ": " + index));
// Outputs:
// 3: 2
// 2: 1
// 1: 0
forEachWithIndex with a range (OrderedIterable)
There is an overload of forEachWithIndex
which takes an inclusive from
and to
index range in addition to the ObjectIntProcedure
.
MutableList<ObjectIntPair<String>> result =
Lists.mutable.empty();
MutableList<String> list =
Lists.mutable.with("1", "2", "3", "4", "5");
list.forEachWithIndex(1, 3,
(each, index) -> result.add(
PrimitiveTuples.pair(each, index)));
var expected = List.of(
PrimitiveTuples.pair("2", 1),
PrimitiveTuples.pair("3", 2),
PrimitiveTuples.pair("4", 3));
Assertions.assertEquals(expected, result);
This method can be used to iterate both forward and reverse, based on the range that is specified. The following code will execute in reverse because the from
index is greater than the to
index.
MutableList<ObjectIntPair<String>> result =
Lists.mutable.empty();
MutableList<String> list =
Lists.mutable.with("1", "2", "3", "4", "5");
list.forEachWithIndex(3, 1,
(each, index) -> result.add(
PrimitiveTuples.pair(each, index)));
var expected = List.of(
PrimitiveTuples.pair("4", 3),
PrimitiveTuples.pair("3", 2),
PrimitiveTuples.pair("2", 1));
Assertions.assertEquals(expected, result);
zipWithIndex
This method takes all of the elements of the collection and “zips” them together with their indices into Pair
instances.
MutableList<String> list = Lists.mutable.with("1", "2", "3");
MutableList<Pair<String, Integer>> zipped = list.zipWithIndex();
var expected = List.of(
Tuples.pair("1", 0),
Tuples.pair("2", 1),
Tuples.pair("3", 2));
Assertions.assertEquals(expected, zipped);
collectWithIndex (OrderedIterable)
This method allows a developer to collect up all of the elements of a collection with their indices to a new collection. This is a more general, and potentially useful form of zipWithIndex
, where the result can be any type the developer wants, not just a Pair<Integer, Integer>
. In the following example, the collection is collected into a new collection of StringIntPair
record instances.
record StringIntPair(String value, int index){};
MutableList<String> list = Lists.mutable.with("1", "2", "3");
MutableList<StringIntPair> collected =
list.collectWithIndex(StringIntPair::new);
var expected = List.of(
new StringIntPair("1", 0),
new StringIntPair("2", 1),
new StringIntPair("3", 2));
Assertions.assertEquals(expected, collected);
The collectWithIndex method takes an ObjectIntToObjectFunction as a parameter.
Arriving in Eclipse Collections 11.0
After writing this blog, I decided it was time to add selectWithIndex
and rejectWithIndex
methods to the OrderedIterable
and ListIterable
interfaces. When Eclipse Collections 11.0 is released, developers will be able to filter both inclusively and exclusively using predicates that take both the element and index as parameters (ObjectIntPredicate
).
The following example demonstrates how to divide a list into separate lists based on even and odd indexes.
ImmutableList<Integer> list = Lists.immutable.with(1, 2, 3, 4, 5);
ImmutableList<Integer> evenIndexes =
list.selectWithIndex((each, index) -> index % 2 == 0);
ImmutableList<Integer> oddIndexes =
list.rejectWithIndex((each, index) -> index % 2 == 0);
Assert.assertEquals(Lists.immutable.with(1, 3, 5), evenIndexes);
Assert.assertEquals(Lists.immutable.with(2, 4), oddIndexes);
The selectWithIndex
and rejectWithIndex
methods are available in the Eclipse Collections 11.0.0.M3 milestone release.
Summary
In this blog, I demonstrate different approaches for iterating over List
instances with both their elements and indices. In plain Java, your only options today are using external iterators. With Eclipse Collections, developers have the option to use a few specialized internal iterators that pass elements and indexes as parameters to the functional interfaces they are provided.
Enjoy!
I am a Project Lead and Committer for the Eclipse Collections OSS project at the Eclipse Foundation. Eclipse Collections is open for contributions. If you like the library, you can let us know by starring it on GitHub.
Other Articles You may like to explore: