Pure Danger Tech


navigation
home

Iterator idioms

06 Sep 2007

Here is some old school iterator style (written here with generics for comparison with later examples): [source:java]

Iterator iter = list.iterator();

while(iter.hasNext()) {

Foo foo = iter.next();

foo.bar();

}

[/source]

A better alternative is to move the Iterator construction into the loop. This is better because it reduces the scope of the iterator instance to just the loop. Using this style eliminates a class of potential bugs / confusing code that can arise from reusing that iterator instance after the loop ends. [source:java]

for(Iterator iter = list.iterator(); iter.hasNext(); ) {

Foo foo = iter.next();

foo.bar();

}

[/source]

Of course, in Java 5+ you can push that even farther by hiding the use of the iterator completely: [source:java]

for(Foo foo : list) {

foo.bar();

}

[/source]

This also assumes that the list was created with a generic collection and is typed as List. This is the sweet spot for generics and the new Java 5 for loop.

If you drop down a level, it’s interesting to look at the bytecode comparison as well. The fun thing is that these three are basically all equivalent at the bytecode level. The notion of “block scope” is really a matter of Java language scope, not really one that translates into the bytecode. The bytecode for these three examples is essentially identical: [source:java]

L0

ALOAD 1: list

INVOKEINTERFACE List.iterator() : Iterator

ASTORE 2: iter

L1

GOTO L2

L3

ALOAD 2: iter

INVOKEINTERFACE Iterator.next() : Object

CHECKCAST Foo

ASTORE 3: foo

L4

ALOAD 3: foo

INVOKEVIRTUAL Foo.bar() : void

L2

ALOAD 2: iter

INVOKEINTERFACE Iterator.hasNext() : boolean

IFNE L3

L5

RETURN

L6

[/source]

For those not conversant at the bytecode level, let’s walk through the first column. In the byte code, everything comes down to the local variables (which are numbered) and the stack. When a method is called, the stack contains a local variable #0, which is always “this” and subsequent local variables for each argument passed to the method.

  • L0 – Label 0, beginning of method
  • ALOAD 1: list – Read local variable #1 (the list) and push onto the stack
  • INVOKEINTERFACE List.iterator() : Iterator – Invoke the interface method on the object on the stack and place the result back on the stack.
  • ASTORE 2: iter – Store the top of the stack into local variable #2
  • L1 – Label 1, beginning of method
  • GOTO L2 – Go to end loop termination check. (And here you thought Java didn’t have goto!)
  • L3 – Label 3, beginning of loop
  • ALOAD 2: iter – Load local variable #2 (iter) onto stack
  • INVOKEINTERFACE Iterator.next() : Object – Invoke the next method on iter and place result on stack
  • CHECKCAST Foo – Check that the returned object is a Foo
  • ASTORE 3: foo – Store foo from stack into local variable 3
  • L4 – Label 4, ready to call foo.bar()
  • ALOAD 3: foo – Load local var #3 (foo) onto stack
  • INVOKEVIRTUAL Foo.bar() : void – Invoke foo.bar()
  • L2 – Label 2, check loop termination condition
  • ALOAD 2: iter – Load iter onto stack
  • INVOKEINTERFACE Iterator.hasNext() : boolean – Invoke hasNext()
  • IFNE L3 – If result is not equal to 0 (false), then go to label L3
  • L5 – Label 5, done with the loop
  • RETURN – Return from method
  • L6 – Label 6, end of method

Notice how the notion of scope in the loop is completely absent from this code?

FYI, the original byte code had LINENUMBER bytecode instructions which I removed to shorten things up. Many of the labels exist solely for use by the LINENUMBER instructions added in debug mode, so many of these labels are not strictly necessary.