In this article, we will do an in-depth exploration of Iterator Design Pattern in Java. So, let’s get started.
What is Iterator Design Pattern in Java ?
Iterator Design Pattern in Java allows to traverse through a collection of objects without exposing the underlying representation.
Example : Iterator Design Pattern in Java
We will try to create a Iterator for traversing through a Container containing some kind of elements. We will have operations like next,hasNext,previous,hasPrevious,current and remove in our customised Iterator.
Class Diagram: Iterator Design Pattern in Java
Java Code
// Iterator Interface
package com.design.iterator;
public interface Iterator {
public T next();
public boolean hasNext();
public boolean hasPrevious();
public T current();
public T previous();
public void remove();
}
// IteratorImpl Class
package com.design.iterator;
import java.util.List;
public class IteratorImpl implements Iterator {
private List elements;
private int position = -1;
public IteratorImpl(List elements) {
this.elements = elements;
}
@Override
public T next() {
if (elements.size() == 0) {
System.out.println("The list is empty");
return null;
}
if (elements.size() > position + 1) {
position = position + 1;
return current();
}
return null;
}
@Override
public boolean hasNext() {
if (elements.size() == 0) {
System.out.println("The list is empty");
return false;
}
return (elements.size() > position + 1);
}
@Override
public T current() {
if (elements.size() == 0) return null;
if (position < 0) {
System.out.println("The list is empty");
return null;
}
return elements.get(position);
}
@Override public T previous() {
if (elements.size() == 0) {
System.out.println("The list is empty");
return null;
}
if ((position - 1) >= 0) {
position = position - 1;
return current();
}
return null;
}
@Override
public boolean hasPrevious() {
if (elements.size() == 0) {
System.out.println("The list is empty");
return false;
}
return (position - 1) >= 0;
}
@Override
public void remove() {
elements.remove(position);
if (elements.isEmpty()) {
position = -1;
}
if (position + 1 == elements.size()) {
position = position - 1;
}
}
}
// IContainer Interface
package com.design.iterator;
import java.util.List;
public interface IContainer {
public List getElements();
public void addElement(T element);
public void removeElement(T element);
public Iterator createIterator();
}
// Container Class
package com.design.iterator;
import java.util.ArrayList;
import java.util.List;
public class Container implements IContainer {
private List elements = new ArrayList < > ();
@Override
public List getElements() {
return elements;
}
@Override
public void addElement(T element) {
elements.add(element);
}
@Override
public void removeElement(T element) {
elements.remove(element);
}
@Override
public Iterator createIterator() {
Iterator iterator = new IteratorImpl(elements);
return iterator;
}
}
// IteratorDemo Class
package com.design.iterator;
public class IteratorDemo {
public static void main(String[] args) {
IContainer container = new Container();
container.addElement("Gyan");
container.addElement("Rochit");
container.addElement("Vivek");
container.addElement("Prabhakar");
Iterator iterator = container.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
// Output
Gyan
Rochit
Vivek
Prabhakar
Use-Cases for Iterator Design Pattern in Java
- Collection Traversal: Iterating through elements of a collection, such as arrays, lists, sets, or maps.
- Database Query Results: Navigating through rows or records retrieved from a database query.
- File Processing: Reading and processing data from files or streams in a sequential manner.
- Tree Traversal: Exploring nodes in tree structures, like binary trees or hierarchical data.
- Menu Navigation: Implementing menu navigation in user interfaces.
Pros of Iterator Design Pattern in Java
- Separation of Concerns: Decouples collection traversal logic from the client code, promoting modular design.
- Single Responsibility: Each iterator class has a single responsibility: managing iteration over a specific collection.
- Flexibility: Allows different ways to traverse the same collection without modifying the client code.
- Reusable Code: Iterator classes can be reused across various collections, reducing code duplication.
- Enhanced Abstraction: Hides the underlying data structure details, focusing on iteration operations.
- Concurrency: Can be adapted for concurrent access to collections without changing client code.
Cons of Iterator Design Pattern in Java
- Performance Overhead: May introduce slight performance overhead due to the additional layer of abstraction.
- Complexity for Simple Collections: Might be overkill for simple collections where foreach loops suffice.
- Limited Control: Limited control over the iteration process compared to manual loop control.
Best Practices for Iterator Design Pattern in Java
- Clear Interface: Design an iterator interface or abstract class that defines iteration methods.
- Iterator Classes: Create concrete iterator classes that implement the iterator interface for specific collections.
- Collection Abstraction: Ensure that the collection classes expose a common interface for iterators.
- Consistent Naming: Follow a consistent naming convention for iterator methods across collection classes.
- Optimize Iterators: Implement optimizations like lazy loading for resource-intensive operations.
- Fail-Fast Iterators: Consider implementing fail-fast iterators for concurrent collections to handle concurrent modifications.
- Immutable Collections: Provide read-only iterators for immutable collections to ensure data integrity.
- Documentation: Document the intended use and behavior of iterators and collection classes.
- Testing: Test iterator classes for various collection scenarios to ensure correct behavior.
- Avoid Reinventing the Wheel: Utilize built-in Java iterators from standard libraries whenever possible.
Conclusion: Iterator Design Pattern in Java
In this article, we delved into the intricacies of the Iterator Design Pattern in Java, uncovering its utility in traversing collections without exposing their underlying structure. Through the illustration of a custom Iterator implementation for a container of elements, we demonstrated how the pattern abstracts the iteration process, promoting modularity and separation of concerns. By showcasing the application of this pattern in various use cases, such as collection traversal, database query results, and file processing, we highlighted its versatility in different scenarios.
The pros of the Iterator Design Pattern, including separation of concerns, flexibility, and reusability, underscored its advantages in promoting clean code and modular design. Conversely, we acknowledged the cons, such as potential performance overhead and limited control over the iteration process, which should be considered in specific contexts.
To ensure effective implementation, we offered a set of best practices, ranging from designing clear iterator interfaces to optimizing iterators and providing fail-fast mechanisms for concurrency. By adhering to these guidelines and embracing built-in Java iterators from standard libraries when suitable, developers can harness the power of the Iterator Design Pattern to enhance code readability, maintainability, and flexibility.
Must Read Design Pattern Articles:
- Professional Tips: Top 10 Design Principles in Java 2023
- SOLID principles in Java: A Comprehensive Guide in 2023
- Design Patterns in Java: For Efficient Development 2023
Recommended Books:
- “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (also known as the Gang of Four book)
- “Head First Design Patterns” by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra