Learn to use the Iterator Design Pattern in Java. Iterator design pattern is a behavioural design pattern that lays down a way to access elements of a collection sequentially. It provides a way for us to implement the accessibility of elements without exposing the underlying representation of the container.
A container can be a list, set, multiset, trees, and graphs, and by underlying representation we mean the way the container has been composed with its methods and properties. If you have come across iterators or “iterables” before, they are in a more general sense something that can be looped over, using a standard loop.
“So, what is new in this? We can iterate over anything. Well, not really there are edge cases where we have to follow an approach such as the Iterator design pattern in order to create a clean design of our code.
For understanding the magic behind iterators in java you need to understand the Iterator design pattern. In this article we are going to see what is the iterator design pattern, benefits of the iterator design pattern, and the iterator design pattern example in java.
What is the Iterator Design Pattern?
Java Provides some built-in containers to store and access the elements. For example – the Array List or a simple Array object can do that for us. Generally, we instantiate an array object and feed into the list whatever data we need to loop over later. But what if we want to create a custom list.
Say, we are creating an application for a Toy Store and it has an inventory object that contains the log of items that came in and went out and also the list of toys that needs to be sold. Now, we could do that using a for loop quite easily but the thing is if in the future we may need to make modifications to our application and instead of using an array if we are to use some other iterable collection, a lot of changes would be needed.
With an array we can get the size, index of the element in the array in one way, and if it were some other collection like a hash table then the way to get those properties might be different. Now, to get away with all these hassles we already have a solution for it, “the iterator design pattern”.
Read: "What is facade design pattern in Java?"
As we mentioned in the opening paragraph that the iterator design pattern provides a way to access the elements of a collection object sequentially without exposing its underlying representation.
This means that we need to have some interface that can be implemented by our custom inventory list so that we can just iterate over it like an array, array list, or a hash table as per the need. This makes our application’s code structure loosely coupled. Some points to be noted about the Iterator Design Pattern:
-
The Iterator design pattern allows us to separate out all the logic for iterating over a collection.
-
There are different collection objects and all of them need their own iterator. The reason is each collection has its own structure. In an array the iterator logic traverses the elements by moving from one memory location to another.
-
In Java, there are iterators for every collection. Each of the iterators in the collection extends an iterator interface and the collections extend the iterable interface. Java itself follows the Iterator design pattern when it comes to its collection.
We will try and implement our version of iterator design pattern by using what java has to offer via the iterator and the iterable interface.
Iterator Design Pattern Example in Java
First, we will try and implement without the iterator design pattern in action, so that we can see where exactly the problems start to begin.
1. The Toy Class
Our toy class consists of methods that store the name of the toy and return the name of the toy.
Fig - iterator design pattern example in java – Toy Class
2. The Inventory Class
As discussed earlier, we want our inventory to be like an iterable. So, that we can pass it around and the loop can traverse through it outputting the intended collections.
-
It consists of two collections – Toy List and Log Array List.
-
The getter methods do as the names suggest – get the size of toy array and get a toy element from the array.
-
The add method adds toy to the list and also adds the “log of the toy to the log array list”
-
The remove method removes the toy from the list and adds the info to the log that the toy has been removed.
-
The printLog method is an example of for each loop that works on the log array list object.
Fig - iterator design pattern example in java – Inventory class
3. Test our Toy store
Fig - iterator design pattern example in java – Toy Store Class
As you can see in our display inventory method of our Toy store class, we have not used the for each loop that says, “here is the inventory, I am looping through it”.
It is not possible to do that for reasons we have mentioned below. This code works just fine, but the problem is we are giving out too much of information related to our inventory (using methods directly from) and that is considered a bad practice. Also, our Toy store is highly coupled to the Inventory class because any changes we make in the Inventory class will eventually lead us to modify the Toy Store class.
For example, if we were to modify our inventory to contain a set of toys instead of a list, our Toy store’s displayInventory would have broken. What we want is a way to simply say, here is the inventory, be it any type of collection, loop over it and the displayInventory method does only what it is intended to do. How can we do that? Before moving any further lets first understand what does the for each loop do in java.
Read: "What is Mediator Design Pattern?"
The for each loop generally has this pattern:
Fig - iterator design pattern example in java – For each loop syntax
It is a syntactic sugar that turns into:
Fig - iterator design pattern example in java – For each loop under the hood
See, java is already using the iterator design pattern, all we need to do is reuse the Iterator interface. Now, because our inventory class is not implementing the Iterator interface, when we try to use the for each loop to our elements inside the inventory class, it does not have access to the iterator method as seen in the above snippet.
Read: "When to Use Composite Design Pattern in Java"
In order to make that work we need to make some adjustments in our inventory class:
Fig - iterator design pattern example in java – Using the Iterable Interface
So, all we have done is implemented the Iterable interface and overridden the iterator method to return the result of “toys. iterator()” method (Note: toys is the list of toys). Now, we can use the for each loop without exposing the underlying details of the list.
Read: "All you need to know about Template Method Design Pattern"
Our Toy store class will be slightly different, instead of for loop we can now use the for each loop.
Fig - iterator design pattern example in java – Using for each loop for inventory traversal
Now, we have not exposed the underlying implementation of the inventory class. And the displayInventory method treats inventory as a collection because it has implemented the Iterable interface. The Iterator design pattern cannot get as simpler as this.
Read: "Decorator design pattern in java with examples"
What have we achieved doing this?
-
We have supported variations in the traversal of a collection/s. Using the Iterator design pattern any type of collection can be traversed without having to know the underlying representation.
-
The applicability of such a design pattern is that it can be used by compilers semantic checking while traversing a tree data structure at one time and a different collection at another time using a common pattern.
-
Also, we can achieve a highly cohesive code design with loosely coupled classes. At the end reducing dependencies is very important.
Read: "Factory Design Patterns in Java"
The short version of this long article is that the Iterator design pattern solves a common problem of looping over collections. In order to traverse different collections, the structure of code becomes repetitive and less maintainable because every collection is different.
With the help of polymorphic behaviour, iterable interfaces made it easier to implement complex collections and traverse through it, thereby solving the problem of high coupling. Also, the iterator design pattern promotes the principle of abstraction by hiding the underlying structure of the collection.
Read: "Singleton Design Pattern – A thoughtful dive into Object Structures and Creation"