Sunday, November 2, 2008

Iterators, and the Enhanced For Loop

As soon as you've learned about the Iterator, Java lets you hide it while still using it. Once you get past the initial complexity of this, the result is clean, elegant code.

-------------------------------------------------------
import java.util.*;

public class ExampleClass {

public static void main(String[] args){

ArrayList list1 = new ArrayList();
list1.add("STRING1");
list1.add("STRING2");
list1.add("STRING3");

TestMethod(list1);
}

static void TestMethod(Collection c) {
for (Iterator i = c.iterator(); i.hasNext();){
System.out.println (i.next()); }
}
}

-------------------------------------------------------

Let's review the ideas from the previous post. Object c instantiates an ArrayList of Strings. ArrayList is an implementation of Collection, which extends Iterable. Iterable is an interface that includes method iterator(), which returns an Iterator object. We're assigning this Iterator object to i, then we're invoking its hasNext() and next() methods to iterate through the Collection. Got it?

It all works, yet there is some clutter here. The iterator variable "i" occurs three times in each loop, which allows for various kinds of subtle mistakes.

In comparison, the enhanced for loop clears out all of this clutter. Take a look at the following code:

void TestMethod(Collection c) {
for (String s : c) { System.out.println (s); }
}

When you see the colon (:) read it as “in.” The loop above reads as “for each String s in Collection c.” This term "For Each" is a general idiom for traversing items in a collection, and is found other object oriented languages such as Perl, PHP, Python, .NET, and many others. Whether you refer to it as the enhanced for loop or the "For Each" loop, it's all the same. This enhanced for loop doesn't require you to explicitly declare the Iterator, but behind the scenes, the compiler is using it implicitly.

This enhanced for loop can also be used with arrays. Instead of hiding the iterator, it hides the index variable. For example, here's a way to calculate the sum of the values in an int array:

// Returns the sum of the elements in the array

int getSum(int[] a) {
int sum = 0;
for (int i : a) // "for each integer 'i' in array 'a' "
sum+= i;
return sum;
}

Before we wrap up Iterators, there are a few more points to discuss.

- - - - -

import java.util.*;

public class ExampleClass {
public static void main(String[] args) {
ArrayList list1 = null;
for (String s : list1) {
System.out.println("Next String: " + s); }
}
}

Will this code compile? The answer is yes, but a null pointer exception will be thrown. The compiler doesn't do null pointer checks before iterating over collections.

- - - - -

What happens if the underlying Collection is modified while the iteration is in progress? I suppose this is roughly equivalent to asking what happens if you modify the track just before a train passes over it. Maybe nothing. Or maybe a train wreck.

How to prevent this? So far we've seen two of the three methods in the iterator interface, hasNext() and next(). The third method is remove(). This method removes the last element returned by the iterator, and can only be invoked once for each call to next().

Here's an example:

static void TestMethod(Collection c) {
for (Iterator i = c.iterator(); i.hasNext(); i.next() )
{ i.remove(); }

As long as you use the remove() method, you should be safe. If you try to do something else, you're on your own.

The Java API documenation puts it this way:

"The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method."

sounds ominous, doesn't it?

Now for a few last details. If the remove method is part of the Iterator, and if the Iterator is hidden within the design of the enhanced for loop, how can you remove objects from the collection while you're iterating through it? The answer is, you can't.

This is a limitation of the enhanced for loop. If you need to call remove(), then don't hide your Iterator.

As for other types of changes to the Collection, I'm not yet clear how you'd create them anyway. If you try to assign a new object to your collection during the iteration, the new object will NOT be assigned to the collection element.

For example:

--------------------------------------------------------
import java.util.*;


class Demo {

int x;

Demo(int t)
{x = t;}

void setValue (int t) { x = t; }

int getValue () { return x; }

}


public class ExampleClass {

public static Collection DemoCollection() {

ArrayList list = new ArrayList();

list.add(new Demo(1));
list.add(new Demo(2));
list.add(new Demo(3));
return list;
}

public static void main(String[] args) {
ExampleClass ec = new ExampleClass();
Collection list = DemoCollection();
for (Demo d : list) {
d = new Demo(5);}
for (Demo d : list) {
System.out.println(d.getValue());
}
}
}

--------------------------------------------------------

This program prints out

run:
1
2
3

As you can see, the original values are still intact.


Question:

In the code above, what happens if you replace

for (Demo d : list) {
d = new Demo(5);}

with

for (Demo d : list) {
d.setValue(5);}

?

Will the output change? If so, why is this?

No comments: