Iterators
In any container class, you must have a way to put things in and a way to get things out. After all, thats the primary job of a containerto hold things. In the ArrayList, add( ) is the way that you insert objects, and get( ) is one way to get things out. ArrayList is quite flexible; you can select anything at any time, and select multiple elements at once using different indexes.
If you want to start thinking at a higher level, theres a drawback: You need to know the exact type of the container in order to use it. This might not seem bad at first, but what if you start out using an ArrayList, and later on you discover that because of the features you need in the container you actually need to use a Set instead? Or suppose youd like to write a piece of generic code that doesnt know or care what type of container its working with, so that it could be used on different types of containers without rewriting that code?
The concept of an iterator (yet another design pattern) can be used to achieve this abstraction. An iterator is an object whose job is to move through a sequence of objects and select each object in that sequence without the client programmer knowing or caring about the underlying structure of that sequence. In addition, an iterator is usually whats called a light-weight object: one thats cheap to create. For that reason, youll often find seemingly strange constraints for iterators; for example, some iterators can move in only one direction.
The Java Iterator is an example of an iterator with these kinds of constraints. Theres not much you can do with one except:
- Ask a container to hand you an Iterator using a method called
iterator( ). This Iterator will be ready to return the
first element in the sequence on your first call to its next( )
method.
- Get the next object in the sequence with next( ).
- See if there are any more objects in the sequence with
hasNext( ).
- Remove the last element returned by the iterator with
remove( ).
Thats all. Its a simple implementation of an iterator, but still powerful (and theres a more sophisticated ListIterator for Lists). To see how it works, lets revisit the CatsAndDogs.java program from earlier in this chapter. In the original version, the method get( ) was used to select each element, but in the following modified version, an Iterator is used:
//: c11:CatsAndDogs2.java
// Simple container with Iterator.
package c11;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class CatsAndDogs2 {
private static Test monitor = new Test();
public static void main(String[] args) {
List cats = new ArrayList();
for(int i = 0; i < 7; i++)
cats.add(new Cat(i));
Iterator e = cats.iterator();
while(e.hasNext())
((Cat)e.next()).id();
}
} ///:~
You can see that the last few lines now use an Iterator to step through the sequence instead of a for loop. With the Iterator, you dont need to worry about the number of elements in the container. Thats taken care of for you by hasNext( ) and next( ).
As another example, consider the creation of a general-purpose printing method:
//: c11:Printer.java
// Using an Iterator.
import java.util.*;
public class Printer {
static void printAll(Iterator e) {
while(e.hasNext())
System.out.println(e.next());
}
} ///:~
Look closely at printAll( ). Note that theres no information about the type of sequence. All you have is an Iterator, and thats all you need to know about the sequence: that you can get the next object, and that you can know when youre at the end. This idea of taking a container of objects and passing through it to perform an operation on each one is powerful and will be seen throughout this book.
The example is even more generic, since it implicitly uses the Object.toString( ) method. The println( ) method is overloaded for all the primitive types as well as Object; in each case, a String is automatically produced by calling the appropriate toString( ) method.
Although its unnecessary, you can be more explicit using a cast, which has the effect of calling toString( ):
System.out.println((String)e.next());
In general, however, youll want to do something more than call Object methods, so youll run up against the type-casting issue again. You must assume youve gotten an Iterator to a sequence of the particular type youre interested in, and cast the resulting objects to that type (getting a run-time exception if youre wrong).
We can test it by printing Hamsters:
//: c11:Hamster.java
public class Hamster {
private int hamsterNumber;
public Hamster(int hamsterNumber) {
this.hamsterNumber = hamsterNumber;
}
public String toString() {
return "This is Hamster #" + hamsterNumber;
}
} ///:~
//: c11:HamsterMaze.java
// Using an Iterator.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class HamsterMaze {
private static Test monitor = new Test();
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 0; i < 3; i++)
list.add(new Hamster(i));
Printer.printAll(list.iterator());
monitor.expect(new String[] {
"This is Hamster #0",
"This is Hamster #1",
"This is Hamster #2"
});
}
} ///:~
You could write printAll( ) to accept a Collection object instead of an Iterator, but the latter provides better decoupling.
Unintended recursion
Because (like every other class) the Java standard containers are inherited from Object, they contain a toString( ) method. This has been overridden so that they can produce a String representation of themselves, including the objects they hold. Inside ArrayList, for example, the toString( ) steps through the elements of the ArrayList and calls toString( ) for each one. Suppose youd like to print the address of your class. It seems to make sense to simply refer to this (in particular, C++ programmers are prone to this approach):
//: c11:InfiniteRecursion.java
// Accidental recursion.
// {RunByHand}
import java.util.*;
public class InfiniteRecursion {
public String toString() {
return " InfiniteRecursion address: " + this + "\n";
}
public static void main(String[] args) {
List v = new ArrayList();
for(int i = 0; i < 10; i++)
v.add(new InfiniteRecursion());
System.out.println(v);
}
} ///:~
If you simply create an InfiniteRecursion object and then print it, youll get an endless sequence of exceptions. This is also true if you place the InfiniteRecursion objects in an ArrayList and print that ArrayList as shown here. Whats happening is automatic type conversion for Strings. When you say:
"InfiniteRecursion address: " + this
The compiler sees a String followed by a + and something thats not a String, so it tries to convert this to a String. It does this conversion by calling toString( ), which produces a recursive call.
If you really do want to print the address of the object in this case, the solution is to call the Object toString( ) method, which does just that. So instead of saying this, youd say super.toString( ).