Iterator Design Pattern In Java
Join the DZone community and get the full member experience.
Join For FreeToday, I will discuss a relatively simple and very commonly used behavioral design pattern called — Iterator Design Pattern. Iterator Pattern provides a simple way to iterate through the collection of objects.
Iterator Design Pattern
- The Iterator Design Pattern is one of twenty-three well known GoF design patterns provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
- Iterator Design Pattern provides an Iterator object to traverse a collection/container and access its member objects.
- Iterator Design Pattern is a relatively simple design pattern, which we use almost in every project.
- Few container requires an algorithm to access the member elements. Iterator Design Pattern decouples any such algorithm from the container.
- The algorithm can be written separately to use by the Iterator and hence can be use by any container which supports that kind of Iterator object.
- The Iterator Design Pattern provides flexible and reusable solution of traversing member objects of a container/collection. That make our collection object easier to implement, change, test and reuse.
- Iterator object also facilitate removing of member object while traversing the collection. So, it's one of the solution of ConcurrentModificationException.
Here, the aggregate is nothing but the container/collection which offers Iterator to traverse itself.
Suppose, we have a List of names as below:
xxxxxxxxxx
List<String> names = new ArrayList<String>();
names.add("Ajay");
names.add("Vijay");
names.add("Martin");
names.add("Racheal");
names.add("Kim");
Now to traverse the elements of it, we can use while loop like:
xxxxxxxxxx
Iterator<String> namesIterator = names.iterator();
while (namesIterator.hasNext()) {
System.out.println("Hello! " + namesIterator.next());
}
or by using for loop:
xxxxxxxxxx
for (Iterator namesIterator = names.iterator(); namesIterator.hasNext();) {
System.out.println("Hello! " + namesIterator.next());
}
Here, in both cases, we need to check availability of container member element by using hasNext()
method before moving to next element by using next()
method. Otherwise we will get NoSuchElementException.
There is also a super easy way of doing same by using for-each loop:
xxxxxxxxxx
for (String name : names) {
System.out.println("Hello! " + name);
}
For-each loops are tailor made and do not require checking collection boundaries while traversing.
Now, some examples to understand the implementation of the Iterator Pattern. I will use Java's Iterator interface in the example and will create only aggregate interface to get the instance of Iterator object.
Code for Container interface (deals with String containers since return type of the method is iterator of strings):
xxxxxxxxxx
package org.trishinfotech.iterator.example1;
import java.util.Iterator;
public interface Container {
public Iterator<String> getIterator();
}
Now, suppose we have a NamesRepository class which implements Container interface as below:
xxxxxxxxxx
package org.trishinfotech.iterator.example1;
import java.util.Iterator;
public class NamesRepository implements Container {
private String names[] = { "Ajay", "Vijay", "Martin", "Racheal", "Kim" };
public NamesRepository() {
super();
}
public Iterator<String> getIterator() {
return new CollectionofNamesIterate(this);
}
public String[] getNames() {
return names;
}
public static class CollectionofNamesIterate implements Iterator<String> {
private int currentElement = 0;
private NamesRepository namesRepository;
public CollectionofNamesIterate(NamesRepository namesRepository) {
super();
this.namesRepository = namesRepository;
}
public boolean hasNext() {
if (currentElement < namesRepository.names.length) {
return true;
}
return false;
}
public String next() {
if (this.hasNext()) {
return namesRepository.names[currentElement++];
}
return null;
}
}
}
Here, please note that I have created concrete implementation of Iterator interface as public static inner class of the repository class itself.
Now the Main program to execute and test the above code.
xxxxxxxxxx
package org.trishinfotech.iterator.example1;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
NamesRepository repository = new NamesRepository();
Iterator<String> repositoryIterator = repository.getIterator();
while (repositoryIterator.hasNext()) {
System.out.println("Hi! " + repositoryIterator.next());
}
// or in an easier way by using for each loop
String[] names = repository.getNames();
for (String name : names) {
System.out.println("Hello! " + name);
}
}
}
Actually, it is availability of tailor made for-each loop which makes the processing easiest. And to achieve that for-each uses implicit iterator object with the help of Iterable interface.
Now, one more example of Books Collection to understand the Iterator pattern.
Book Library Application Using Iterator Design Pattern
I think by now we are clear on the Iterator Pattern and its uses. By the example below, I will try to eliminate the doubts if any while implementing the Iterator Pattern.
Code for Book class:
xxxxxxxxxx
package org.trishinfotech.iterator;
public class Book {
private String language;
private String title;
private String author;
private long ISBN;
private double price;
public Book() {
super();
}
public Book(String language, String title, String author, long ISBN, double price) {
this();
this.language = language;
this.title = title;
this.author = author;
this.ISBN = ISBN;
this.price = price;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public long getISBN() {
return ISBN;
}
public void setISBN(long iSBN) {
ISBN = iSBN;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Book [language=").append(language).append(", title=").append(title).append(", author=")
.append(author).append(", ISBN=").append(ISBN).append(", price=").append(price).append("]");
return builder.toString();
}
}
Code for BookCollection interface (aggregate interface):
xxxxxxxxxx
package org.trishinfotech.iterator.example2;
import java.util.Iterator;
public interface BookCollection {
public Iterator<Book> iterator();
}
The method name can be like getIterator()
or simply iterator()
. Now, we have two different versions of book-collections.
- Variable number of books - BookLibrary.
- Fixed and limited number of books - BookStore.
Code for BookLibrary class:
xxxxxxxxxx
package org.trishinfotech.iterator.example2;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class BookLibrary implements BookCollection {
protected List<Book> books;
public BookLibrary() {
super();
books = new ArrayList<Book>();
}
public boolean addBook(String language, String title, String author, long ISBN, double price) {
Book book = new Book(language, title, author, ISBN, price);
return books.add(book);
}
public boolean addBook(Book book) {
return books.add(book);
}
public List<Book> getBooks() {
return books;
}
public Iterator<Book> iterator() {
return books.iterator();
}
}
Here I am using Java provided Iterator object for the Collection.
Now the code for BookStore class:
xxxxxxxxxx
package org.trishinfotech.iterator.example2;
import java.util.Iterator;
public class BookStore implements BookCollection {
protected static int MAX_BOOKS = 1000;
protected int totalBooks = 0;
protected Book[] books;
public BookStore() {
super();
books = new Book[MAX_BOOKS];
}
public boolean addBook(String language, String title, String author, long ISBN, double price) {
Book book = new Book(language, title, author, ISBN, price);
if (totalBooks < (MAX_BOOKS - 1)) {
books[totalBooks++] = book;
return true;
} else {
System.out.println("BookStore is full and can't accept any more books!");
return false;
}
}
public boolean addBook(Book book) {
if (totalBooks < (MAX_BOOKS - 1)) {
books[totalBooks++] = book;
return true;
} else {
System.out.println("BookStore is full and can't accept any more books!");
return false;
}
}
public Book[] getBooks() {
return books;
}
public Iterator<Book> iterator() {
return new BookIterator(books);
// or simply use the below line to avoid writing explicit implementation of the iterator class.
// return Arrays.stream(books).iterator();
}
}
Here, I am using explicit implementation of concrete Iterator class to demonstrate how we can write it. We can also use implicit Iterator object as we did in the BookLibrary class.
Now the code for BookIterator class which offers iterating over array of books:
xxxxxxxxxx
package org.trishinfotech.iterator.example2;
import java.util.Iterator;
public class BookIterator implements Iterator<Book> {
protected Book[] books;
protected int currentBook = 0;
public BookIterator(Book[] books) {
this.books = books;
}
public boolean hasNext() {
return (currentBook < books.length && books[currentBook] != null);
}
public Book next() {
return books[currentBook++];
}
}
Now, it's ready to execute and test the output. So, lets write a Main program:
xxxxxxxxxx
package org.trishinfotech.iterator.example2;
import java.util.Arrays;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
Book[] books = new Book[5];
books[0] = new Book("English", "Head First Java, 2nd Edition", "Kathy Sierra", 10123758943l, 10.5d);
books[1] = new Book("English", "Effective Java (3rd Edition)", "Bloch Joshua", 34422235432l, 8.65d);
books[2] = new Book("English", "Mastering Java Machine Learning", "Dr Uday Kamath", 2123765476l, 21.45d);
books[3] = new Book("English", "Cloud-Native Java", "Kenny Bastani ", 21332343434l, 28.99d);
books[4] = new Book("English", "Java 9 Modularity", "Paul Bakker", 1645243587l, 36.68d);
BookStore store = new BookStore();
BookLibrary library = new BookLibrary();
Arrays.stream(books).forEach(book -> {
store.addBook(book);
library.addBook(book);
});
System.out.println("Printing Book details of Book Store");
System.out.println(
"-------------------------------------------------------------------------------------------------");
printBooks(store.iterator());
System.out.println("Printing Book details of Book Library");
System.out.println(
"-------------------------------------------------------------------------------------------------");
printBooks(library.iterator());
}
private static void printBooks(Iterator<Book> iterator) {
// String language, String title, String author, long ISBN, double price
System.out.printf("%10s | %35s | %15s | %15s | %10s\n", "Language", "Title", "Author", "ISBN", "Price");
System.out.println(
"-------------------------------------------------------------------------------------------------");
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.printf("%10s | %35s | %15s | %15s | %10.2f\n", book.getLanguage(), book.getTitle(),
book.getAuthor(), book.getISBN(), book.getPrice());
}
System.out.println(
"-------------------------------------------------------------------------------------------------");
System.out.println();
}
}
And here's the output:
xxxxxxxxxx
Printing Book details of Book Store
-------------------------------------------------------------------------------------------------
Language | Title | Author | ISBN | Price
-------------------------------------------------------------------------------------------------
English | Head First Java, 2nd Edition | Kathy Sierra | 10123758943 | 10.50
English | Effective Java (3rd Edition) | Bloch Joshua | 34422235432 | 8.65
English | Mastering Java Machine Learning | Dr Uday Kamath | 2123765476 | 21.45
English | Cloud-Native Java | Kenny Bastani | 21332343434 | 28.99
English | Java 9 Modularity | Paul Bakker | 1645243587 | 36.68
-------------------------------------------------------------------------------------------------
Printing Book details of Book Library
-------------------------------------------------------------------------------------------------
Language | Title | Author | ISBN | Price
-------------------------------------------------------------------------------------------------
English | Head First Java, 2nd Edition | Kathy Sierra | 10123758943 | 10.50
English | Effective Java (3rd Edition) | Bloch Joshua | 34422235432 | 8.65
English | Mastering Java Machine Learning | Dr Uday Kamath | 2123765476 | 21.45
English | Cloud-Native Java | Kenny Bastani | 21332343434 | 28.99
English | Java 9 Modularity | Paul Bakker | 1645243587 | 36.68
-------------------------------------------------------------------------------------------------
Now before I conclude this article, I like to discuss one more topic here.
Iterable Interface:
This is included in Java 5 and is part of java.lang package. An object that implements this interface allows it to be the target of the for-each loop.
Let's see with the same Book Library example to understand how we can implement it.
All we need to do is: We have to make our BookStore and BookLibrary as sub-type of Iterable interface. Iterable interface will act as aggregator for us for Iterator Pattern.
And we have to implement only below method:
xxxxxxxxxx
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
Please notice that its with generics just like Iterator interface.
Book Library Application Using Iterator Design Pattern (Iterable Interface)
There is no change in the Book class. So, will use the same. Also, there is no need to have BookCollection interface since Iterable will serve that purpose.
Now code for BookLibrary class:
xxxxxxxxxx
package org.trishinfotech.iterator.example3;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.trishinfotech.iterator.Book;
public class BookLibrary implements Iterable<Book> {
protected List<Book> books;
public BookLibrary() {
super();
books = new ArrayList<Book>();
}
public boolean addBook(String language, String title, String author, long ISBN, double price) {
Book book = new Book(language, title, author, ISBN, price);
return books.add(book);
}
public boolean addBook(Book book) {
return books.add(book);
}
public List<Book> getBooks() {
return books;
}
public Iterator<Book> iterator() {
return books.iterator();
}
}
Code for BookStore class:
xxxxxxxxxx
package org.trishinfotech.iterator.example3;
import java.util.Iterator;
import org.trishinfotech.iterator.Book;
import org.trishinfotech.iterator.example2.BookIterator;
public class BookStore implements Iterable<Book> {
protected static int MAX_BOOKS = 1000;
protected int totalBooks = 0;
protected Book[] books;
public BookStore() {
super();
books = new Book[MAX_BOOKS];
}
public boolean addBook(String language, String title, String author, long ISBN, double price) {
Book book = new Book(language, title, author, ISBN, price);
if (totalBooks < (MAX_BOOKS - 1)) {
books[totalBooks++] = book;
return true;
} else {
System.out.println("BookStore is full and can't accept any more books!");
return false;
}
}
public boolean addBook(Book book) {
if (totalBooks < (MAX_BOOKS - 1)) {
books[totalBooks++] = book;
return true;
} else {
System.out.println("BookStore is full and can't accept any more books!");
return false;
}
}
public Book[] getBooks() {
return books;
}
public Iterator<Book> iterator() {
return new BookIterator(books);
// or simply use the below line to avoid writing explicit implementation of the iterator class.
// return Arrays.stream(books).iterator();
}
}
Almost the same? Isn't it? And we are done! And you know what our previous code of Main will also work without any change. But, I like to show the simplicity of the code.
Now its time to write Main program to execute and test the output:
package org.trishinfotech.iterator.example3;
import java.util.Arrays;
import org.trishinfotech.iterator.Book;
public class Main {
public static void main(String[] args) {
Book[] books = new Book[5];
books[0] = new Book("English", "Head First Java, 2nd Edition", "Kathy Sierra", 10123758943l, 10.5d);
books[1] = new Book("English", "Effective Java (3rd Edition)", "Bloch Joshua", 34422235432l, 8.65d);
books[2] = new Book("English", "Mastering Java Machine Learning", "Dr Uday Kamath", 2123765476l, 21.45d);
books[3] = new Book("English", "Cloud-Native Java", "Kenny Bastani ", 21332343434l, 28.99d);
books[4] = new Book("English", "Java 9 Modularity", "Paul Bakker", 1645243587l, 36.68d);
BookStore store = new BookStore();
BookLibrary library = new BookLibrary();
Arrays.stream(books).forEach(book -> {
store.addBook(book);
library.addBook(book);
});
System.out.println("Printing Book details of Book Store");
System.out.println(
"-------------------------------------------------------------------------------------------------");
printBooks(store);
System.out.println("Printing Book details of Book Library");
System.out.println(
"-------------------------------------------------------------------------------------------------");
printBooks(library);
}
private static void printBooks(Iterable<Book> books) {
// String language, String title, String author, long ISBN, double price
System.out.printf("%10s | %35s | %15s | %15s | %10s\n", "Language", "Title", "Author", "ISBN", "Price");
System.out.println(
"-------------------------------------------------------------------------------------------------");
for (Book book : books) {
System.out.printf("%10s | %35s | %15s | %15s | %10.2f\n", book.getLanguage(), book.getTitle(),
book.getAuthor(), book.getISBN(), book.getPrice());
}
System.out.println(
"-------------------------------------------------------------------------------------------------");
System.out.println();
}
}
Well you have it! I hope you are clear with examples on Iterator Design Pattern.
Source Code can be found here: Iterator Design Pattern Source Code
Liked this article? Don't forget to press that like button. Happy coding!
Need more articles, please visit my profile: Brijesh Saxena
Opinions expressed by DZone contributors are their own.
Comments