1. Overview
In this tutorial, we’ll look at the usage of Iterable and Iterator interfaces in Java and the differences between them.
2. Iterable Interface
Iterable interface belongs to the java.lang package. It represents a data structure that can be iterated over.
The Iterable interface provides a method that produces an Iterator. When using an Iterable, we cannot get an element by index. Similarly, we cannot get the first or the last elements from the data structure as well.
All collections in Java implement the Iterable interface.
2.1. Iterate Over an Iterable
We can iterate over elements inside a collection using the enhanced for loop, also called the for-each loop. However, only objects that implement an Iterable interface can be used within such a statement. It’s also possible to iterate over elements using the while statement in combination with an Iterator.
Let’s see an example of iterating over elements in a List using the for-each statement:
List<Integer> numbers = getNumbers();
for (Integer number : numbers) {
System.out.println(number);
}
Similarly, we can use the forEach() method in combination with lambda expressions:
List<Integer> numbers = getNumbers();
numbers.forEach(System.out::println);
2.2. Implementing the Iterable Interface
Custom implementations of the Iterable interface can come in handy when we have custom data structures that we’d like to iterate over.
Let’s begin by creating a class representing a shopping cart that will hold elements in an array. We’ll not call the for-each loop directly on the array. Instead, we’ll implement the Iterable interface. We don’t want our clients to depend on the chosen data structure. If we provide clients the ability to iterate, we can easily use a different data structure without clients having to change code.
The ShoppingCart class implements the Iterable interface and overrides its iterate() method:
public class ShoppingCart<E> implements Iterable<E> {
private E[] elementData;
private int size;
public void add(E element) {
ensureCapacity(size + 1);
elementData[size++] = element;
}
@Override
public Iterator<E> iterator() {
return new ShoppingCartIterator();
}
}
The add() method stores elements in an array. Due to an array’s fixed size and capacity, we expand the maximum number of elements using the ensureCapacity() method.
Each invocation of the iterator() method on the custom data structure produces a new instance of an Iterator. We create a new instance since an iterator is responsible for maintaining the current iteration state.
By providing a concrete implementation of the iterator() method, we can use an enhanced for statement to iterate over objects of the implemented class.
Now, let’s create an inner class inside the ShoppingCart class that represents our custom iterator:
public class ShoppingCartIterator implements Iterator<E> {
int cursor;
int lastReturned = -1;
public boolean hasNext() {
return cursor != size;
}
public E next() {
return getNextElement();
}
private E getNextElement() {
int current = cursor;
exist(current);
E[] elements = ShoppingCart.this.elementData;
validate(elements, current);
cursor = current + 1;
lastReturned = current;
return elements[lastReturned];
}
}
Lastly, let’s create an instance of our iterable class and use it in the enhanced for loop:
ShoppingCart<Product> shoppingCart = new ShoppingCart<>();
shoppingCart.add(new Product("Tuna", 42));
shoppingCart.add(new Product("Eggplant", 65));
shoppingCart.add(new Product("Salad", 45));
shoppingCart.add(new Product("Banana", 29));
for (Product product : shoppingCart) {
System.out.println(product.getName());
}
3. Iterator Interface
Iterator is a member of the Java Collections Framework. It belongs to the java.util package. This interface allows us to retrieve or remove elements from a collection during the iteration.
In addition, it has two methods that help iterate over the data structure and retrieve its elements – next() and hasNext().
Moreover, it has a remove() method, which removes the current element pointed to by the Iterator.
Finally, the forEachRemaining(Consumer<? super E> action) method performs the given action for each remaining element inside the data structure.
3.1. Iterate Over Collection
Let’s see how to iterate over a List of Integer elements. In the example, we’ll combine the while loop and methods hasNext() and next().
The List interface is a part of Collection and, therefore, it extends the Iterable interface. To get an iterator from the collection, we simply need to call the iterator() method:
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(40);
Iterator<Integer> iterator = numbers.iterator();
Furthermore, we can check if the iterator has any remaining elements by calling the hasNext() method. Afterward, we can get an element by calling the next() method:
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
The next() method returns the next element from the iteration. On the other hand, if there is no such element, it throws NoSuchElementException.
3.2. Implementing the Iterator Interface
Now, we’ll implement the Iterator interface. Custom implementation can be useful when we need to iterate over a collection using conditional element retrieval. For instance, we can use a custom iterator for iterating over odd or even numbers.
To illustrate, we’re going to iterate over prime numbers from the given collection. As we know, a number is considered a prime if it’s only divisible by one and by itself.
First, let’s create a class that contains a collection of numeric elements:
class Numbers {
private static final List<Integer> NUMBER_LIST =
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
Furthermore, let’s define a concrete implementation of the Iterator interface:
private static class PrimeIterator implements Iterator<Integer> {
private int cursor;
@Override
public Integer next() {
exist(cursor);
return NUMBER_LIST.get(cursor++);
}
@Override
public boolean hasNext() {
if (cursor > NUMBER_LIST.size()) {
return false;
}
for (int i = cursor; i < NUMBER_LIST.size(); i++) {
if (isPrime(NUMBER_LIST.get(i))) {
cursor = i;
return true;
}
}
return false;
}
}
Concrete implementations are usually created as inner classes. In addition, they’re responsible for maintaining the current iteration state. In the example above, we stored the current position of the next prime number inside the instance variable. Every time we call the next() method, the variable will contain an index of the upcoming prime number.
Any implementation of the next() method should throw a NoSuchElementException exception when there are no more elements left. Otherwise, the iteration can cause unexpected behavior
Let’s define a method inside the Number class that returns a new instance of the PrimeIterator class:
public static Iterator<Integer> iterator() {
return new PrimeIterator();
}
Finally, we can use our custom iterator within the while statement:
Iterator<Integer> iterator = Numbers.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
4. Differences Between Iterable and Iterator
To sum up, the following table shows the main differences between the Iterable and the Iterator interfaces:
Iterable
Iterator
Represents a collection that can be iterated over using a for-each loop
Represents an interface that can be used to iterate over a collection
When implementing an Iterable, we need to override the iterator() method
When implementing an Iterator, we need to override the hasNext() and next() methods
Doesn’t store the iteration state
Stores the iteration state
Removing elements during the iteration isn’t allowed
Removing elements during the iteration is allowed
5. Conclusion
In this article, we looked at the differences between the Iterable and the Iterator interfaces in Java and their usage.
As always, the source code for the examples is available over on GitHub.