Sunday, November 04, 2007

Java 5+ Iterator Adapters

I've been working with Java Generics more lately, and have run smack into the "Wall of Erasure". I'm referring to the fact that generics disappear in the bytecode. As you probably know, generics were a targeted feature at a time when Sun was attempting to avoid changing the Java Virtual Machine (JVM). They really wanted to maintain backward compatibility. So, this compromise was put forth to maintain backward compatibility. In a classic case of irony, other changes forced a change the JVM in the same release that gave us generices, but the compromised version of generics stayed. So we are stuck with half an implementation of generics.

Recently, I've been doing work with interfaces and extending interfaces, as in:

public interface Superclass {
// ...
}

public interface Subclass extends Superclass {
// ...
}

I have classes which implement a third interface:

public interface DoesStuff {
Iterator<Superclass> iterator();
// ...
}


You would think that an implementation of DoesStuff could return an Iterator for the iterator method:

public class MyClass implements DoesStuff {
// ...
private Set<Subclass> myThings = new HashSet<Subclass>();
// ...
public Iterator<Superclass> iterator() {
return myThings.iterator();
}
// ...
}

Unfortunately, the Wall of Erasure means we can't do this. Try it for yourself if you don't believe me.


So, what to do? Happily, there is a simple answer. We create an adapter that adapts the subclass iterator to the superclass iterator:


/**
* Adapts a derived class iterator to a base class.
*/
public class IteratorAdapter<B, D extends B> implements Iterator<B> {
private Iterator<D> d;

public IteratorAdapter(Iterator<D> aD) {
d = aD;
}

public boolean hasNext() {
return d.hasNext();
}

public B next() {
return d.next();
}

public void remove() {
d.remove();
}
}

The sad thing is that this shouldn't be necessary. It's trivial. It shouldn't have to be written by developers. The simplicity of this is a clear sign that we need to tear down the Wall of Erasure.

Will it happen any time soon? I hope so. Though I'm hearing talk that "erasing erasure" is probably off the table for Java 7. That would be a truly sad thing.

5 comments:

Ricky Clarkson said...

Actually this is the wall of not understanding generics properly, not erasure.

Let's change DoesStuff to:

public interface DoesStuff {
....Iterator<? extends Superclass> iterator();
}

And live happily ever after.

Brian Gilstrap said...

Well, I live happily ever after...or until I want to make DoesStuff Iterable. If I do that,I get a type clash:

public interface DoesStuff extends Iterable<Superclass> {
     Iterator<? extends Superclass> iterator();
}


I haven't found a way to genericize the extends of Iterable to make it work. Perhaps you have another solution, Ricky?

Anonymous said...

You can simply use a generic type parameter ... for instance ...

public interface DoesStuff<T extends Superclass> extends Iterable<T> {
Iterator<T> iterator();
}

Brian Gilstrap said...

The suggestion by anonymous doesn't seem to help. If I do as suggested I still can't make my concrete class Iterable.

I'd love to solve this problem without needing the iterator adaptor (and without obfuscating the code with too much generics gobblygook).

Perhaps someone could provide a complete example that works? If they send it to me via email, I'll format it in a blog posting and give them credit.

roschler said...

Thanks Brian. This old (by Internet time standards) post of yours came in real handy when I wanted to return a String iterator for a SortedMap's keys.