Tuesday, December 5, 2006

Java's Enhanced For Loop Mystery

I like Java 5's new enhanced for loop. It makes the code that iterates over collections and arrays less verbose. Anything that reduces Java's verbosity is a good thing in my book.

Yesterday I wondered if the Java 5 enhanced for loop calls the collection expression once or every time through the loop. I searched for the answer online, and couldn't find one. Every example I found assigned the collection or array to a variable first and then used that variable in the expression, like this:

  String[] list = {"a", "b", "c"};
  for (String item : list) {
    // ...
  }

What I was looking for was a statement like, "the list expression is only evaluated once". After a few minutes, I realized that it would be faster to write a small program to determine the answer. (See Just Try It.)

import java.util.*;

public class Test {

  public Collection<String> stringCollection() {
    System.out.println("I'm creating a new list now.");
    // Don't get me started on the verbosity of the next line...
    ArrayList<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    return list;
  }

  public static void main(String[] args) {
    Test t = new Test();
    for (String s : t.stringCollection())
      System.out.println(s);
  }
}

With a simple javac Test.java && java Test I had the answer: stringCollection() is only called once.

4 comments:

David R. MacIver said...

I think the syntax "for (Foo foo : bar) { doStuff(); }", where bar is iterable, is a simple macro for the following code:

for (Iterator myIterator = bar.iterator(); iterator.hasNext();)
{
Foo foo = myIterator.next();
}

This is certainly what running a decompiler over the generated code suggests.

Given this, the behaviour you saw is pretty much what should be expected.

For an array it needs to use a bit of a different approach which it handles separately. It seems to look something like this:

Object aobj1[] = bar;
int i = aobj1.length;
for(int j = 0; j < i; j++)
{
Object obj = aobj1[j];
}

(It's of course possible to get an iterator out of an array, e.g. with Arrays.asList(myArray).iterator(), but the JIT has special magic for dealing with this sort of for loop, so it would be a bit wasteful to do it that way when we're already writing macros).

Most of the magic which 1.5 provided are little short of macros and a bit of syntactic sugar over the oldschool ways of doing things. Pity they didn't just add a decent macro system in userland and be done with it. :-)

David R. MacIver said...

Oh, and on the verbosity of creating lists, if you don't mind your list being fixed size, Arrays.asList("a", "b", "c", "d", ...) will create a fixed sized list with the given elements (backed by an array).

If you do mind your lists being fixed size, it's sometimes still easier to write new ArrayList(Arrays.asList(stuff)).

Jim Menard said...

Thanks for the background info. What decompiler did you use (I've never tried finding one for Java before)?

I'd normally say "yes" to macros, but I'm not sure how they would mesh with Java code. Are there any suggestions that include examples of the syntax?

David R. MacIver said...

I used jad. It's a bit out of date (doesn't pick up on 1.5 features), but that's actually not a bad thing when you're trying to reverse engineer 1.5 features.

There seem to be a lot of Java decompilers - with the byte code being as close to Java as it is, decompilation is very easy to do - but I've never had much luck with most of them. I think I settled on jad a while back because it was in portage so I had easy access to it. It works easily when it works, but can be a bit tempermental at byte code generated by anything other than javac.

I don't know what a good macro system for Java would look like to be honest. The syntax is probably too irregular for a really great one. Maybe something like MetaML's angle bracketed code as values approach, but that's probably dreaming.