Java provides many ways to iterate over a List
. Some of them are using :
- Stream API
- ListIterator interface
- Enhanced for-loop
- Simple for-loop
We are not going through the basics of each of the above ways as it is beyond the scope of this article, and most of us are already well aware.
In this post, we will compare all the looping methods against the same set of data to compare their relative performances.
1. Different Methods to Loop Through a List
We are listing down 4 different ways which are in my knowledge.
1.1. Stream API
Java 8 Stream API provides ways to iterate over a collection and operate over each element. Stream can be used as an alternative to the for-loop.
private static List<Integer> list = new ArrayList<>();
list.stream().forEach(consumerAction);
1.2. Enhanced for-loop
In this technique, advanced for-each statement introduced in Java 5 is used.
private static List<Integer> list = new ArrayList<>();
for(Integer i : list)
{
// do other stuff
}
1.3. ListIterator Interface
private static List<Integer> list = new ArrayList<>();
list.listIterator().forEachRemaining(consumerAction);
1.4. Simple for-loop
private static List<Integer> list = new ArrayList<>();
int size = list.size();
for(int j = 0; j < size ; j++)
{
//do stuff
}
2. Performance Comparison
We are creating an ArrayList
and populating it with one million Integer
instances. Then we will iterate through the list using all the above ways. This way we will be able to understand the difference in the performance.
2.1. Execution environment
- Java 16
- Eclipse 2021-06
2.2. Source Code
package com.howtodoinjava.core.basic;
import java.util.ArrayList;
import java.util.List;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.infra.Blackhole;
public class ForLoopPerformanceTest
{
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
private static List<Integer> list = new ArrayList<>();
static
{
for(int i=0; i < 1_000_000; i++)
{
list.add(i);
}
}
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingStream(Blackhole blackhole) {
list.stream().forEach(i -> blackhole.consume(i));
}
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingIterator(Blackhole blackhole) {
list.listIterator().forEachRemaining(i -> blackhole.consume(i));
}
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingForEachLoop(Blackhole blackhole) {
for(Integer i : list)
{
blackhole.consume(i);
}
}
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingSimpleForLoop(Blackhole blackhole) {
for(int i = 0; i < list.size() ; i++)
{
blackhole.consume(list.get(i));
}
}
}
When the above JMH based benchmarking runs, the following is the output in the console:
Benchmark Mode Cnt Score Error Units
ForLoopPerformanceTest.usingForEachLoop thrpt 20 259.008 ± 17.888 ops/s
ForLoopPerformanceTest.usingIterator thrpt 20 256.016 ± 10.342 ops/s
ForLoopPerformanceTest.usingSimpleForLoop thrpt 20 495.308 ± 12.866 ops/s
ForLoopPerformanceTest.usingStream thrpt 20 257.174 ± 15.880 ops/s
Clearly, using the simple for-loop is way ahead in the performance. Rest other three ways provide similar performance numbers.
3. Conclusion
Although the simple for-loop provides the best performance, other looping methods provide much better readability.
Also, we are using the loop with over one million items in the list which is not a very practical scenario in most of the applications.
So if there are not millions of items in the list, use the new Java features such as Stream API or enhanced for-loops.
Happy Learning !!