While cleaning old Java code I ran across this pre-Tiger pattern several times:
class Thingies { private final List things = new ArrayList(); // Other useful fields public List getThings() { return Collections.unmodifiableList(things); } public void addThing(final Thing thing) { things.add(thing); } // Other useful methods }
Essentially the author intended to simulate a sort of List
decorated with other, useful information for a business object. Hiding the list internally is a Good Thing and helps abstraction. Using unmodifiableList
is great.
But with Tiger and generics, I can do better:
class Thingies implements Iterable<Thing> { private final List things = new ArrayList(); // Other useful fields public Iterator<Thing> iterator() { return things.iterator(); } public void addThing(final Thing thing) { things.add(thing); } // Other useful methods }
Now I can use the class with Tiger foreach syntax:
for (final Thing thing : instanceOfThingies) { // Do something with thing. }
This advice is available in many places on the Internet, and is good advice. But did you notice something wrong with the example? What happens with thingies.iterator().remove()
? I introduced an abstraction leak.
Let's fix the leak:
public Iterable<Thing> iterator() { return new Iterator<Thing>() { final Iterator<Thing> it = things.iterator(); public boolean hasNext() { return it.hasNext(); } public Thing next() { return it.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
Now if the caller decides to use thingies.iterator() directly, they still cannot modify the underlying list.
A neat trick
Continuing this theme, I also ran across a neat trick. The old code has a small class hierarchy which the simulated list managed. Typical usage:
class FourWheeler { } class Sedan extends FourWheeler { } class Pickup extends FourWheeler { } // In another class far, far away: final Iterator sedans = fourWheelers.getList().iterator(); while (sedans.hasNext()) { final Sedan sedan = (Sedan) it.next(); } // In yet another class even more far, far away: final Iterator pickups = fourWheelers.getList().iterator(); while (pickups.hasNext()) { final Pickups pickup = (Pickup) it.next(); }
The thing is, the four wheelers list manager only held one type of four wheeler at a time so the caller was safe in casting as long as they were careful in updating the list. But this is very wordy. One approach is to generify the list manager:
class FourWheelers<T extends FourWheeler> { private final List<T> list = new ArrayList<T>(); // Rest of class uses T instead of FourWheeler. }
This is a great approach to the problem for new code. But there is a smaller change one can make to the class that also works. Add a new method:
public Iterable<Sedan> sedans() { return new Iterable<Sedan>() { public Iterator<Sedan> iterator() { final Iterator it = list.iterator(); public boolean hasNext() { return it.hasNext(); } @SuppressWarnings({"unchecked"}) public Sedan next() { return (Sedan) it.next(); } public void remove() { throw new UnsupportedOperationException(); } } }; }
And likewise for Pickup
. You can abstract this common pattern to a base class and simplify the methods further:
public Iterable<Sedan> sedans() { return new FourWheelerIterable<Sedan>(list.iterator()); }
Usage is now like this with all the nasty casting hidden away:
// Back in a far, far away class. for (final Sedan sedan : fourWheelers.sedans()) { // Do something with sedan. }
UPDATE: Jörg has a great observation in the comments: you may need more than one kind of generic Iterable<T>
for a complex class.
3 comments:
Rock on. I think it would be slightly hotter if instead of doing the cast in the next() method, you declared the inner iterator as an Iterator of T, and then you don't need a cast at all.
The cast is still needed somewhere. The list is a list of base class, but the iterator runs over derived classes.
The point is to hide the casting inside the utility method/class so that the user/caller needs no casting in their application code.
Be careful: You can only implement Iterable once! You can not do something like:
class Customer
implements Iterable< Contact >, Iterable< Order >
I like this:
public List< Contact > getContacts()
So I can do:
for(Contact contact : customer.getContacts())
which is also more readable as:
for(Contact contact : customer)
when have to use a superclass or interface:
for(Object obj : customer)
What do I iterate here?
Post a Comment