Is there a conventional iterator interface in Java which separates traversal from element access?

144 Views Asked by At

For my application, element access is expensive, so java.util.Iterator is no use. I want something more like C++ iterators, where I can move the pointer around without returning an element. Is there something in the standard library like this, or has some de facto standard interface evolved through custom? (If not, please don't waste your time posting code snippets - I'm quite able to think up reasonable names which will do the job).

2

There are 2 best solutions below

0
On BEST ANSWER

(Since no one came around to write a proper answer, I'll expand my comments into an answer.)

To the extent of my knowledge there's no interface in the standard API that suits you (i.e. an interface with something like iterator.skip()). The best solution using standard API methods I believe, would be to do yourList.sublist(startIndex).iterator() for instance.

I think that, if possible, you should consider creating your own iterator interface. (Note that you can let it extend the java.util.Iterator interface to be able to use it as an ordinary iterator if needed.)

Finally, and this is mostly for future readers that have to use java.util.Iterator and have to access the underlying list lazily, you can use a java.lang.reflect.Proxy. Note that Proxy objects doesn't always play well with serialization / persistence libraries, and it requires that you work with interfaces. An example follows:

Here's a slow OnDiskList that fetches line n in each call to get(int n):

class OnDiskList extends AbstractList<CharSequence> {

    // Slow list access method: Linear search for n:th line.
    @Override
    public CharSequence get(int n) {
        System.out.println("Fetching line " + n + "...");
        try (Stream<String> lines = Files.lines(Paths.get("big-file.txt"))) {
            return lines.skip(n).findFirst().get();
        } catch (IOException shouldBeHandled) {
            return null;
        }
    }

    @Override
    public int size() {
        return 5;
    }
}

Here's how the output looks when using an ordinary iterator:

Iterator<CharSequence> normalIter = onDiskList.iterator();
normalIter.next(); // Skip
normalIter.next(); // Skip
normalIter.next(); // Skip
System.out.println(normalIter.next()); // Print

Output:

Fetching line 0...
Fetching line 1...
Fetching line 2...
Fetching line 3...
Line 3 in big-file.txt

Now to the magic: Here's a ProxyingIterator that hides the object behind a Proxy that accesses the list lazily:

class ProxyingIterator implements Iterator<CharSequence> {

    List<CharSequence> slowList;
    int pos = 0;

    public ProxyingIterator(List<CharSequence> slowList) {
        this.slowList = slowList;
    }

    @Override
    public boolean hasNext() {
        return pos < slowList.size();
    }

    @Override
    public CharSequence next() {
        return (CharSequence) Proxy.newProxyInstance(
                        CharSequence.class.getClassLoader(),
                        new Class[] { CharSequence.class },
                        new LazyLoadingInvocationHandler(pos++));
    }

    private class LazyLoadingInvocationHandler implements InvocationHandler {

        int index;
        CharSequence loadedObject = null;

        public LazyLoadingInvocationHandler(int index) {
            this.index = index;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            if (loadedObject == null)
                loadedObject = slowList.get(index);
            try {
                return method.invoke(loadedObject, args);
            } catch (Exception shouldNotHappen) {
                shouldNotHappen.printStackTrace();
                return null;
            }
        }
    }
}

When using this iterator it looks as follows:

Iterator<CharSequence> proxyingIter = new ProxyingIterator(onDiskList);
proxyingIter.next(); // Skip
proxyingIter.next(); // Skip
proxyingIter.next(); // Skip
System.out.println(proxyingIter.next()); // Print

Output:

Fetching line 3...
Line 3 in big-file.txt
9
On

java.util.Iterator is an interface, not an implementation. The behaviour of your iterator will depend on the collection you are using and how you obtained it.

Most of them should not be doing anything expensive as, for Objects, Java passes around their reference. Whatever you're accessing that is expensive should be abstracted away behind an Object that allows you to refer to it without trying to access it.

If your gripe is instead with the interface and a desire for other methods, then you'll need to come up with and conform to that interface by yourself; java.util.Iterator is the conventional iterator for Java, in part likely for the reasons I've mentioned.