Iterators
I like to iterate, it's great.
- Data structures typically contain a collection of elements.
- It is often useful to be able to traverse that collection of elements.
- It is possible to get an iterator from any collection that implements the Iterable
interface. - The
Iterable<T>
interface is quite simple:
Iterator<T> iterator();
- You may be wondering why you're seeing
<T>
instead of<E>
.- Skip these sub-bullets if you aren't the slight bit interested.
- The
E
inList<E>
refers to the type of element in the list. - The
T
inIterable<T>
refers to the type of element over which we can iterate. - Okay... at least I gave you the option of skipping these sub-bullets.
- So you're probably wondering what you can do with an iterator. If not, you should be.
Iterator<E>
is an interface consisting of three methods:boolean hasNext()
— returnstrue
if another element exists in the collectionE next()
— returns the next element in the collectionvoid remove()
— removes, from the underlying collection, the last element returned by the iterator (the result of the most recentnext()
call)
- That's all great, but who's going to implement the
Iterator<E>
interface?- We need a class to implement the interface, but the class needs to be able to access the elements within the collection.
- Nested/Inner class to the rescue.1
Revisiting the Simplified ArrayList<E>
Example
- Here is how we could add the
Iterable<T>
interface to our simplifiedArrayList<E>
class
@Override
public Iterator<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator implements Iterator<E> {
private int position;
private boolean isLegalToRemove;
private ArrayListIterator(){
position = -1;
isLegalToRemove = false;
}
@Override
public boolean hasNext() {
return size() > (position+1);
}
@Override
public E next() {
if(!hasNext()){
throw new NoSuchElementException("No next element available");
}
isLegalToRemove = true;
return array[++position];
}
@Override
public void remove() {
if(!isLegalToRemove){
throw new IllegalStateException("Must call next() before remove()");
}
isLegalToRemove = false;
ArrayList.this.remove(position--);
}
} // End of ArrayListIterator class
- You might think that we'd need to change the class declaration to this:
public class ArrayList<E> implements List<E>, Iterable<E> {
- We don't need to do this because the
List<E>
interface extends theCollection<E>
interface which extends theIterable<E>
interface.
- We don't need to do this because the
- Let's look at this code for a little bit...
- The
iterator()
method was part of theArrayList<E>
class before, we just hadn't implemented it here. - Our implementation just returns a reference to an object from the
ArrayListIterator
class — the superhero inner class. - The
ArrayListIterator
class and its constructor are declaredprivate
since nobody but theArrayList<E>
class has any business messing with the class directly. - The
position
attribute keeps track of where we are in the collection. - The
isLegalToRemove
attribute is used by theremove
method, and we'll talk about it in a bit. - The constructor forces us to begin one before the first element of the collection.
hasNext()
needs to make sure that theposition
is such that we still have at least one more element in the collection to traverse.- We just make sure that we'll still have an element to return after incrementing the
position
.
- We just make sure that we'll still have an element to return after incrementing the
next()
moves the iterator to the next element and returns a reference to it.- First, we check to make sure that we have an element in the collection (if not we throw a
NoSuchElementException
exception). - Second, we increment the
position
attribute. Note: because we are using the pre-increment operator,position
gets incremented before we evaluate the rest of the expression. If we didposition++
instead, we would get anArrayIndexOutOfBoundsException
exception the first time we callednext()
because it would be trying this:array[-1]
. - Third, we return a reference to the next element in the collection.
- First, we check to make sure that we have an element in the collection (if not we throw a
remove()
is an optional method (meaning you can throw anUnsupportedOperationException
exception instead of implementing it), but theList<E>
interface provides aremove(int index)
method that does just what we want, so we'll call it.2- The
remove()
method should only be called ifnext()
has been called since it the iterator was instantiated and since the lastremove()
call. - As a result, we have introduced the
isLegalToRemove
flag to keep track of when it is legal to callremove()
.isLegalToRemove
is set tofalse
in the constructor and wheneverremove()
is called.isLegalToRemove
is set totrue
whenevernext()
is called.
- The
- The
Enhanced for Loop: The foreach Loop
- Iterators are what make it possible for us to use the enhance
for
loop. - Consider:
List<Integer> list = new ArrayList<>(); for(int i=0; i < 5; ++i) { list.add((int)(Math.random()*25)); } for(Integer number : list) { System.out.println(number); }
- The second
for
loop makes use of an interator to navigate the collection. - It is equivalent to doing this:
Iterator<Integer> itr = list.iterator(); while(itr.hasNext()) { Integer number = itr.next(); System.out.println(number); }
- The second
1
It's tough to take superhero seriously if it has a slash in its name, so I probably should have just said Nested class to the rescue OR Inner class to the rescue.
2
Hopefully you didn't notice that we didn't actually implement that method.