7.5 Making Your Classes Work with for/in
Despite all the collection
options available, there are times when it's still
useful to define your own custom objects. In cases where your objects
represent some sort of collection, it's a good practice to provide a means
of letting other classes iterate over them. In the past, this usually meant
ensuring that your class provided a java.util.Iterator, allowing it to
work with a for loop. With Tiger, you should consider taking a few extra
steps to ensure that your custom objects will work with for/in as well. In
addition to working with Iterator, you'll need to learn a new interface,
java.lang.Iterable.NOTENote that Iterable is in java.lang, not java.util, as you might expect.
7.5.1 How do I do that?
First, familiarize yourself with Example 7-3 and Example 7-4, the
Iterator and Iterable interfaces. You'll need to grasp both of these to
see how the various loops in Java work.
Example 7-3. The Iterator interface
I've obviously stripped this down to the bare essentials; that's all you
package java.util;
public interface Iterator<E> {
public boolean hasNext( );
public E next( );
public void remove( );
}
really need, anyway. These methods should look familiar, although I still
find the generics syntax (<E> and E) a bit odd to look at.Here's Iterable, in the same form:
Example 7-4. The Iterable interface
There are two basic cases in which Iterator and Iterable become
package java.lang;
public interface Iterable<E> {
public java.util.Iterator<E> iterator( );
}
issues when dealing with custom objects:
- Your custom object extends an existing Collection class that already
supports for/in.Your custom object has to handle iteration manually.
7.5.1.1 Extending collection classes
The first case is the easiest
to deal with, as you can essentially steal
behavior from the parent class to do all the work. Example 7-5 shows a
simple class that extends List.
Example 7-5. Extending the LinkedList class
This class doesn't do much in terms of customizationit does require that
package com.oreilly.tiger.ch07;
import java.util.LinkedList;
import java.util.List;
public class GuitarManufacturerList extends LinkedList<String> {
public GuitarManufacturerList( ) {
super( );
}
public boolean add(String manufacturer) {
if (manufacturer.indexOf("Guitars") == -1) {
return false;
} else {
super.add(manufacturer);
return true;
}
}
}
only String be allowed as a parameter (through the extends
LinkedList<String> declaration), and that values passed into the add( )
method have "Guitars" as part of their value. This is a rather hackish way
to ensure manufacturers are supplied, but it's useful for an illustration.
You can now use this class as shown in Example 7-6. The example creates
a new instance of GuitarManufacturerList, seeds it with some sample
data, and then uses for/in to iterate over it. With essentially no work on
your part, you get the benefit of iteration from the superclass, LinkedList.
Example 7-6. Iterating over GuitarManufacturerList
package com.oreilly.tiger.ch07;
import java.io.IOException;
import java.io.PrintStream;
public class CustomObjectTester {
/** A custom object that extends List */
private GuitarManufacturerList manufacturers;
public CustomObjectTester( ) {
this.manufacturers = new GuitarManufacturerList<String>( );
}
public void testListExtension(PrintStream out) throws IOException {
// Add some items for good measure
manufacturers.add("Epiphone Guitars");
manufacturers.add("Gibson Guitars");
// Iterate with for/in
for (String manufacturer : manufacturers) {
out.println(manufacturer);
}
}
public static void main(String[] args) {
try {
CustomObjectTester tester = new CustomObjectTester( );
tester.testListExtension(System.out);
} catch (Exception e) {
e.printStackTrace( );
}
}
}
7.5.1.2 Handling iteration manually
In cases where you're not extending an existing collection class, you've
got a little more work to do. Still, you'll usually find yourself borrowing at
least some behavior from existing collection classes, and avoiding direct
implementation of Iterator. Example 7-7 shows a simple text file reader
that lists the lines of a file when iterated over.NOTEIf you don't pass in "LinkedList <String>" here, and just use
"LinkedList", you'll get compiler warnings indicating a possible type mismatch.
Example 7-7. Custom class that doesn't extend a collection
NOTEThis code sample is from Java in a Nutshell, Fifth Edition (O'Reilly).The interesting work is in the TextFileIterator class, which handles all the
package com.oreilly.tiger.ch07;
import java.util.Iterator;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* This class allows line-by-line iteration through a text file.
* The iterator's remove( ) method throws UnsupportedOperatorException.
* The iterator wraps and rethrows IOExceptions as IllegalArgumentExceptions.
*/
public class TextFile implements Iterable<String> {
// Used by the TextFileIterator class below
final String filename;
public TextFile(String filename) {
this.filename = filename;
}
// This is the one method of the Iterable interface
public Iterator<String> iterator( ) {
return new TextFileIterator( );
}
// This non-static member class is the iterator implementation
class TextFileIterator implements Iterator<String> {
// The stream we're reading from
BufferedReader in;
// Return value of next call to next( )
String nextline;
public TextFileIterator( ) {
// Open the file and read and remember the first line.
// We peek ahead like this for the benefit of hasNext( ).
try {
in = new BufferedReader(new FileReader(filename));
nextline = in.readLine( );
} catch(IOException e) {
throw new IllegalArgumentException(e);
}
}
// If the next line is non-null, then we have a next line
public boolean hasNext( ) {
return nextline != null;
}
// Return the next line, but first read the line that follows it.
public String next( ) {
try {
String result = nextline;
// If we haven't reached EOF yet
if (nextline != null) {
nextline = in.readLine( ); // Read another line
if (nextline == null)
in.close( ); // And close on EOF
}
// Return the line we read last time through.
return result;
} catch(IOException e) {
throw new IllegalArgumentException(e);
}
}
// The file is read-only; we don't allow lines to be removed.
public void remove( ) {
throw new UnsupportedOperationException( );
}
}
public static void main(String[] args) {
String filename = "TextFile.java";
if (args.length > 0)
filename = args[0];
for(String line : new TextFile(filename))
System.out.println(line);
}
}
work of iteration. The first thing to notice is that this iteration is completely
read-onlyremove simply throws an UnsupportedOperationException. This
is a perfectly legal and useful means of ensuring that programmers understand
the use-case your custom classes are designed for. I'll leave you to
work through the rest of the details; the source code is pretty self-explanatory.