1. Introduction

The Iterator and Iterable interfaces are fundamental constructs for working with collections in Java. Practically, each interface provides methods for traversing elements, but they have distinct purposes and usage scenarios.

In this tutorial, we’ll delve into the differences between Iterator.forEachRemaining() and Iterable.forEach() to understand their unique functionalities.

2. The Iterator.forEachRemaining() Method

The Iterator interface provides a way to iterate over a collection of elements sequentially. The forEachRemaining() method of the Iterator interface was introduced in Java 8.

In addition, it provides a concise way to act on each remaining element in the iterator. Besides, it takes a Consumer functional interface as an argument, representing the action performed on each element.

Let’s suppose we have the following employee’s details, and we want to process them to make a simple report:

private final List<String> employeeDetails = Arrays.asList(
    "Alice Johnson, 30, Manager",
    "Bob Smith, 25, Developer",
    "Charlie Brown, 28, Designer"
);
String expectedReport =
    "Employee: Alice Johnson, 30, Manager\n" +
    "Employee: Bob Smith, 25, Developer\n" +
    "Employee: Charlie Brown, 28, Designer\n";

Here, we have initialized a list of employee details and specified an expected report format with each employee’s information formatted as (Employee: Name, Age, Role).

Now, let’s utilize the Iterator.forEachRemaining() method to iterate over the employeeDetails list and generate a report:

@Test
public void givenEmployeeDetails_whenUsingIterator_thenGenerateEmployeeReport() {
    StringBuilder report = new StringBuilder();
    employeeDetails.iterator().forEachRemaining(employee ->
        report.append("Employee: ").append(employee).append("\n")
    );

    assertEquals(expectedReport, report.toString());
}

In this test method, we process each element in the iterator, appending formatted employee information to the StringBuilder report. For each employee detail string in the employeeDetails list, the method appends the prefix “*Employee:*” followed by the employee details and a newline character.

After generating the report, we use the assertEquals() assertion to verify that the generated report (report) matches the expected report (expectedReport).

3. The Iterable.forEach() Method

The Iterable interface in Java represents a collection of objects that can be iterated over. The forEach() method of the Iterable interface was also introduced in Java 8.

*The default method allows us to act as each element in the collection. Like Iterator.forEachRemaining(), it also uses a Consumer functional interface as an argument.*

To provide context, let’s look at the implementation:

@Test
public void givenEmployeeDetails_whenUsingForEach_thenGenerateEmployeeReport() {
    StringBuilder report = new StringBuilder();
    employeeDetails.forEach(employee ->
        report.append("Employee: ").append(employee).append("\n")
    );

    assertEquals(expectedReport, report.toString());
}

Within the forEach() method, we use a lambda expression to append each formatted employee detail to StringBuilder’s report.

Similar to Iterator.forEachRemaining(), the lambda expression here receives each element as input, and we perform the same formatting operation of prefixing “*Employee:*” followed by the employee details and a newline character.

4. Key Differences

The following table succinctly summarizes the differences between Iterator.forEachRemaining() and Iterable.forEach() based on their usage, implementation, and flexibility:

Key Differences

Iterator.forEachRemaining()

Iterable.forEach()

Usage

We can use it to act on each remaining element of an iterator.

We can use it to act on each collection element directly without explicitly using an iterator.

Implementation

Specific to the Iterator interface and operates directly on an iterator instance.

The default method of the iterable interface operates directly on an iterable collection.

Flexibility

It is useful when using an iterator to iterate over a subset of elements from a collection.

It is more convenient to work directly with collections, especially when utilizing lambda expressions.

5. Conclusion

In this article, we discussed both the Iterator.forEachRemaining() and Iterable.forEach() methods to iterate over elements in a collection. Choosing the appropriate method based on whether we’re working directly with an iterator or a collection can be based on user preference.

As always, the complete code samples for this article can be found over on GitHub.