Best way to iterate ArrayList in Java

Every developer uses the Collection in Java, and you must face different challenges or doubts in Java collection. In this post, we will discuss the most common operation on ArrayList, traversing. Java provides several ways to iterate an ArrayList, but one question comes to mind. Which is the Best way to iterate ArrayList in Java?
Of course, each has its own advantages and disadvantages. So, let’s see a practical example and discuss each and every aspect.

To demonstrate an example, we’ll consider an example where we have a list of 10,000 Student objects, and the student object has 5 properties such as id, name, GPA, age, etc.

Here is the student class having some properties.

public class Student {
    int id;
    String name;
    double gpa;
    int age;
    String major;
    String minor;
    boolean enrolled;
    String address;

    public Student(int id, String name, double gpa, int age, String major, String minor, boolean enrolled,
                   String address) {
        this.id = id;
        this.name = name;
        this.gpa = gpa;
        this.age = age;
        this.major = major;
        this.minor = minor;
        this.enrolled = enrolled;
        this.address = address;
    }
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

public class Main {

    public static void main(String[] args) {
        // Initialize ArrayList with 10,000 Student objects
        ArrayList<Student> students = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 10000; i++) {
            students.add(new Student(i, "Student" + i, random.nextDouble() * 4.0, 18 + random.nextInt(5),
                    "Major" + i, "Minor" + i, true, "Address" + i));
        }

        long startTime, endTime;

        // Traditional for loop
        startTime = System.nanoTime();
        for (int i = 0; i < students.size(); i++) {
            Student student = students.get(i);
        }
        endTime = System.nanoTime();
        System.out.println("Traditional For Loop Time: " + (endTime - startTime) + " ns");

        // Enhanced for loop
        startTime = System.nanoTime();
        for (Student student : students) {
        }
        endTime = System.nanoTime();
        System.out.println("Enhanced For Loop Time: " + (endTime - startTime) + " ns");

        // While loop
        startTime = System.nanoTime();
        int i = 0;
        while (i < students.size()) {
            Student student = students.get(i);
            i++;
        }
        endTime = System.nanoTime();
        System.out.println("While Loop Time: " + (endTime - startTime) + " ns");

        // Iterator
        startTime = System.nanoTime();
        Iterator<Student> iterator = students.iterator();
        while (iterator.hasNext()) {
            Student student = iterator.next();
        }
        endTime = System.nanoTime();
        System.out.println("Iterator Time: " + (endTime - startTime) + " ns");

        // Streams
        startTime = System.nanoTime();
        students.stream().forEach(student -> {
        });
        endTime = System.nanoTime();
        System.out.println("Streams Time: " + (endTime - startTime) + " ns");
    }
}

Which Approach is Best to traverse an ArrayList in Java?

In the read operation of ArrayList, the performance difference is negligible because the ArrayList provides constant-time positional access.

But if we consider the modification like removing elements during traversal, then the Iterator approach may be more suitable as it avoids the ConcurrentModificationException and does not require manual index management.

For read-only operations, using the For-Each loop is often the most readable and straightforward, but it doesn’t offer any significant performance advantage over the traditional For loop.

Let’s see one more point here, if you are performing only read elements, all three approaches have similar performance. For removing elements during traversal, using an Iterator is the most efficient and safest.

The For-Each loop is often easier to read and understand.

What about streams?

Streams are designed for expressiveness and can handle complex data transformation tasks. But are they fast? Well, it depends on your specific use case. Streams introduce some overhead compared to a plain loop. This is often negligible but might become significant when you perform simple operations over very large data sets.

Some operations like findAny, findFirst, anyMatch, etc., can be faster with Streams because they allow short-circuiting. But in a plain traversal, this advantage doesn’t apply.

Methods for Traversing ArrayLists in Java

Traditional For Loop

When should you use a traditional loop for ArrayList:

Full Control Over Index: Tradition for loop works based on index variable so have complete control over the index variable. It means you can skip elements, iterate backward, or even change multiple elements in one iteration if necessary.

Modify During Traversal: When you want to modify while iterating over it. For example, you could conditionally remove or add elements based on specific criteria, like eliminating all students who are not enrolled.

Enhanced for loop for ArrayList in Java

The enhanced for loop and for-each loop are the same, It’s also another good choice for iterating over collections.

When should you use Enhanced for loop for ArrayList

Readability: The syntax of the enhanced for loop is clean and clear and is easy to read and understand.

Reduced Chance of Error: Here we don’t need to declare an index variable, define a termination condition, or increment the index. You get all of these out of the box, making it easier for new programmers to pick up and less prone to errors.

Iterator for ArrayList in Java

Accessing elements using an Iterator is very efficient, comparable to direct index-based access in a traditional for loop or an enhanced for loop. The time required for traversal remains the same—linear with respect to the number of elements.

When to use Streams?

  1. Readability: Streams can make complex data manipulation code more readable.
  2. Complex Operations: If you’re performing complex operations like filtering, mapping, etc., then using a stream can lead to cleaner code.
  3. Immutable Operations: Streams work well when you’re working in a functional programming style where immutability is a concern.

Leave a Comment