19. Iterators
It is often necessary to access every element in some thing
Like an array,
Stack
,Queue
, orBag
This is called iterating over the things
This has already been done countless times with arrays
1for (int i = 0; i < someArray.length; i++) {
2 process(someArray[i]);
3}
However, not everything one may want to iterate over is an array
Trying to use a
for
loop for iterating over a linked structure doesn’t work as nicelyInstead, a
while
loop is typically used
1while (currentNode != null) {
2 process(currentNode.getData());
3 currentNode = currentNode.getNext();
4}
19.1. Iterators
Java provides a common uniform way to iterate over things
Iterators are objects that allow iterating over something one element at a time
Get each element in an array
Get each element from a
Bag
There are two important relevant interfaces:
Iterator
— used when creating an iterator objectIterable
— used when creating something one may want to iterate over
19.2. Iterator Interface
Iterator objects are typically very simple
To define an iterator for a class, define the class such that it
implements Iterator<T>
Uses the Iterator interface
There are two abstract methods included in the interface to focus on
T next()
— retrieve the next elementboolean hasNext()
— checks if another element exists
With these, it becomes possible to iterate over elements without needing to worry about the underlying container
1Iterator<T> iterator = arbitraryIterableThing.iterator();
2
3while (iterator.hasNext()) {
4 process(iterator.next());
5}
19.2.1. Array Iterator
Although it is not required to know what the underlying container is when using the iterator
It is required to define an iterator for the underlying container
For example, if defining a collection that makes use of an array, an
ArrayIterator
will need to be defined
5public class ArrayIterator<T> implements Iterator<T> {
6
7 private final int size;
8 private final T[] elements;
9 private int index;
The fields only include
size
— number of elements in the collectionelements
— the array of elements to iterate overindex
— the current index the iterator is at
13 public ArrayIterator(T[] elements, int size) {
14 if (size > elements.length) {
15 throw new IllegalArgumentException("size exceeds array length.");
16 }
17 this.elements = elements;
18 this.size = size;
19 this.index = 0;
20 }
Constructor sets the iterator up to start at the beginning of the array
24 @Override
25 public boolean hasNext() {
26 return index < size;
27 }
hasNext
returns aboolean
indicating if there is an element to retrieveSimply check if the current index (
index
) is less than the number of elements in the array (size
)
31 @Override
32 public T next() {
33 if (!hasNext()) {
34 throw new NoSuchElementException();
35 }
36 T returnElement = elements[index];
37 index++;
38 return returnElement;
39 }
next
returns the next element and updatesindex
It returns the element and sets the iterator up to return the subsequent element when needed
The way this is written, if
next
is called when there are no more elements to retrieve, an exception is thrownNotice that
This iterator can only go in one direction
Once the iterator object hits the end of the collection, it does not reset
To start iterating again, a new iterator would need to be created
Note
There is nothing preventing someone from writing an iterator class that returns the element in some other order. For example, reverse order.
Warning
Consider that the ArrayIterator
has reference to the underlying array container. Generally, iterators should not
modify the collections they are iterating over. Side effects like this are a recipe for disaster.
19.2.2. Linked Iterator
1import java.util.Iterator;
2import java.util.NoSuchElementException;
3
4public class LinkedIterator<T> implements Iterator<T> {
5
6 private Node<T> current;
7
8 public LinkedIterator(Node<T> head) {
9 current = head;
10 }
11
12 @Override
13 public boolean hasNext() {
14 return current != null;
15 }
16
17 @Override
18 public T next() {
19 if (!hasNext()) {
20 throw new NoSuchElementException();
21 }
22 T returnElement = current.getData();
23 current = current.getNext();
24 return returnElement;
25 }
26}
For the
LinkedIterator
, all that is needed is a reference to the current nodeThe constructor sets the current node to reference the head of the linked structure
hasNext
just checks ifcurrent
referencesnull
next
returns the next element and sets the iterator up to return the subsequent element when neededJust like the
ArrayIterator
The iterator only goes in one direction
Once an element is retrieved with
next()
, it is not possible to retrieve it again unless creating a new iterator
Warning
If the Node
class is an internal class, then the LinkedIterator
will need to be internal too.
19.2.3. Collection Iterators
Consider a
SortedBag
If using an
ArraySortedBag
implementation, theiterator
method would need to return anArrayIterator
1 @Override 2 public Iterator<T> iterator() { 3 return new ArrayIterator<>(bag, size()); 4 }
Similarly, the
iterator
method for aLinkedSortedBag
would need to return aLinkedIterator
1 @Override 2 public Iterator<T> iterator() { 3 return new LinkedIterator<>(head); 4 }
Since both versions of the
SortedBag
return anIterator
And since the iterator provides a common way to iterate over the container
It is possible to iterate over an array and linked structure the exact same way
1 Iterator<Integer> iterator = myBag.iterator();
2
3 while (iterator.hasNext()) {
4 process(iterator.next());
5 }
In the end, the actual underlying container has no impact on the code used to iterate
19.2.3.1. toString
Consider the following
toString
for some collection
1 public String toString() {
2 StringBuilder builder = new StringBuilder();
3 Iterator<T> iterator = this.iterator();
4 while(iterator.hasNext()) {
5 builder.append(iterator.next());
6 builder.append(", ");
7 }
8 return builder.toString();
9 }
By looking at the above code, it is not possible to know if this is for an array or linked implementation
This is a fantastic example of abstraction
It is possible to iterate over something (what) without needing to worry about the implementation details (how)
19.3. Iterable Interface
The
Iterator
interface is used for creating an iterator object to iterate over somethingIf making a class that is to be iterated over, then that class will
implement Iterable<T>
For example, asking the collection for an iterator
myBag.iterator()
Within the Iterable interface there is one abstract method —
iterator()
If this interface is implemented correctly, the collection is guaranteed to be iterable
19.3.1. For Each
For things that are iterable, enhanced for loop can be used — for each loop
In general, it looks like this
1for (type refVar: iterableThing) {
2 process(refVar);
3}
An example for our
SortedBag<Integer>
may look like this
1for (Integer x: myBag) {
2 process(x);
3}
Revisiting the
toString()
example from above with an enhanced for loop
1 public String toString() {
2 StringBuilder builder = new StringBuilder();
3 for (T element : this) { // 'this' is the iterable thing
4 builder.append(element);
5 builder.append(", ");
6 }
7 return builder.toString();
8 }
Note
The enhanced for loop is just syntactic sugar for what was already shown above. The enhanced for loop example
1for (Integer x: myBag) { 2 process(x); 3}
means the same thing as
1Iterator<Integer> iterator = myBag.iterator(); 2while (iterator.hasNext()) { 3 process(iterator.next()); 4}
19.4. Equality of Stacks (and Queues)
Consider the
equals
methods fromArrayStack
andLinkedStack
114 @Override
115 public final boolean equals(Object o) {
116 if (this == o) {
117 return true;
118 }
119 if (o == null || getClass() != o.getClass()) {
120 return false;
121 }
122 ArrayStack<?> that = (ArrayStack<?>) o;
123 return Arrays.equals(this.stack, 0, this.size(), that.stack, 0, that.size());
124 }
125
126 @Override
127 public final int hashCode() {
128 int result = Objects.hash(top);
129 for (int i = 0; i < size(); i++) {
130 result = result * 97 + Objects.hashCode(stack[i]);
131 }
132 return result;
133 }
85 @Override
86 public boolean equals(Object o) {
87 if (this == o) {
88 return true;
89 }
90 if (o == null || getClass() != o.getClass()) {
91 return false;
92 }
93 LinkedStack<?> that = (LinkedStack<?>) o;
94 if (this.size() != that.size()) {
95 return false;
96 }
97 Node<?> thisCurrent = this.top;
98 Node<?> thatCurrent = that.top;
99 while (thisCurrent != null && thatCurrent != null) {
100 if (!Objects.equals(thisCurrent.getData(), thatCurrent.getData())) {
101 return false;
102 }
103 thisCurrent = thisCurrent.getNext();
104 thatCurrent = thatCurrent.getNext();
105 }
106 return true;
107 }
108
109 @Override
110 public int hashCode() {
111 int result = Objects.hashCode(size);
112 Node<?> current = top;
113 while (current != null) {
114 result = result * 97 + Objects.hashCode(current.getData());
115 current = current.getNext();
116 }
117 return result;
118 }
It should be possible to check equality between
Stack
objects, regardless of their implementationHowever, with the current implementation, it’s not possible to compare an
ArrayStack
andLinkedStack
A solution to this problem is to make
Stacks
implementiterable
If this is done, there is a common way to iterate over the
Stack
independent of the underlying implementation
1@Override
2public final boolean equals(Object o) {
3 if (this == o) {
4 return true;
5 }
6 if (o == null) {
7 return false;
8 }
9 if (!(o instanceof Stack)) {
10 return false;
11 }
12 Stack<?> that = (Stack<?>) o;
13 if (this.size() != that.size()) {
14 return false;
15 }
16 Iterator<T> thisIterator = this.iterator();
17 Iterator<?> thatIterator = that.iterator();
18 while (thisIterator.hasNext()) {
19 if (!Objects.equals(thisIterator.next(), thatIterator.next())) {
20 return false;
21 }
22 }
23 return true;
24}
19.5. For Next Time
Consider how the
Stack
andQueue
could be made iterableRead Chapter 7
12 pages
19.5.1. Playing Code
Download and play with