Running on Java 22-ea+15-1134 (Preview)
Home of The JavaSpecialists' Newsletter

040Visiting Your Collection's Elements

Author: Dr. Heinz M. KabutzDate: 2002-01-31Java Version: 1.4Category: Language
 

Abstract: When iterating over a heterogeneous collection, one way to differentiate between object types is with instanceof. Another way is to create a special visitor object that can be applied to the objects in the collection.

 

Welcome to the 40th edition of The Java(tm) Specialists' Newsletter, sent to over 2550 Java experts in over 70 countries. Despite my plea that you don't unsubscribe, I had a rather surprising number of unsubscriptions, as programmers expressed their outrage at my audacity by voting with their feet. My views are my own and that of my employer - since I am my own employer ;-). I'm working on a program at the moment and I do make sure that our JavaDocs are up to date by running a Doclet that tells me where I've forgotten a tag. Whenever I change a method, I nuke the comments, and then the Doclet tells me where I need to add a comment again.

The ideas in this newsletter were spawned by Inigo Surguy (inigosurguy@hotmail.com) who works in Lemington Spa in the United Kingdom. Inigo is the UK Head of Research and Development of Interactive AG. Inigo also pointed out BCEL to me, used to change byte code "on the fly". I will write about some application of that in future.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Visiting Your Collection's Elements

Warning: the comparator we use in this code is not implemented according to the specification. It can happen that the elements are stored out of order. A more complicated matching mechanism needs to be used to dispatch to the correct methods.

The Problem

I'm getting tired. Not tired of writing newsletters, but tired of Java. Tired of writing the same code over and over again. For example:

// ...
Iterator it = ages.iterator();
while(it.hasNext()) {
  Integer age = (Integer)it.next();
  System.out.println("Now you're " + age +
    ", in 3 years time, you'll be " + (age.intValue() + 3));
}

I don't like that while loop with the iterator. Don't know why I don't like it, it just looks inelegant to me. I like the weird for loop for an iterator even less:

// ...
for(Iterator it = ages.iterator(); it.hasNext();) {
  Integer age = (Integer)it.next();
  System.out.println("Now you're " + age +
    ", in 3 years time, you'll be " + (age.intValue() + 3));
}

Lastly, I don't like downcasting and I don't like the problems that occur when you have different types in a collection.

A Different Way ...

Before looking at a solution, I would like to show how I would use iterators normally:

import java.util.*;

public class OldVisitingIteratorTest {
  public static void main(String[] args) {
    Collection c = new LinkedList();
    for (int i=0; i<3; i++) c.add(new Integer(i));

    Iterator it = c.iterator();
    while(it.hasNext()) {
      // lots of brackets - looks almost like Lisp - argh
      System.out.println(((Integer)it.next()).intValue() + 10);
    }

    c.add(new Float(2.1));
    c.add("Hello");

    it = c.iterator();
    while(it.hasNext()) {
      Object o = it.next();
      if (o instanceof Integer) {
        System.out.println(((Integer)o).intValue() + 10);
      } else if (o instanceof Number) {
        System.out.println(((Number)o).intValue() + 20);
      } else if (o instanceof String) {
        System.out.println(((String)o).toLowerCase());
      } else {
        System.out.println(o);
      }
    }

    it = c.iterator();
    while(it.hasNext()) {
      System.out.println(((Integer)it.next()).intValue() + 10);
    }
  }
}

The output from that code is:

10
11
12
10
11
12
22
hello
10
11
12
Exception in thread "main" java.lang.ClassCastException: java.lang.Float
        at OldVisitingIteratorTest.main(OldVisitingIteratorTest.java:32)

Instead of constructing an Iterator and going through the Iterator and doing some operation on its contents, why not pass in an object with an execute() method that is called with each element? After some speed-typing yesterday, while waiting for my students at a Design Patterns course at the Strand Beach Hotel near Cape Town to finish an exercise, I came up with:

import java.util.*;
import java.lang.reflect.*;

public class VisitingIterator {
  /**
   * Ordering methods in "best-fit" order.
   */
  private static final Comparator METHOD_COMPARATOR =
    new Comparator() {
      public int compare(Object o1, Object o2) {
        Class paramType1 = ((Method)o1).getParameterTypes()[0];
        Class paramType2 = ((Method)o2).getParameterTypes()[0];
        return paramType1.isAssignableFrom(paramType2) ? 1 : -1;
      }
    };

  /**
   * Threadsafe version of visit.
   * @param lock the object on which to synchronize
   * @param task is an Object with an execute(...) : void method
   */
  public void visit(Collection c, Object task, Object lock) {
    synchronized(lock) {
      visit(c, task);
    }
  }

  /**
   * @param task is an Object with an execute(...) : void method
   */
  public void visit(Collection c, Object task) {
    TreeSet methods = new TreeSet(METHOD_COMPARATOR);
    Method[] ms = task.getClass().getMethods();
    for (int i=0; i<ms.length; i++) {
      if (ms[i].getName().equals("execute")
          && ms[i].getParameterTypes().length == 1) {
        methods.add(ms[i]);
      }
    }
    Iterator it = c.iterator();
    while(it.hasNext()) {
      boolean found = false;
      Object o = it.next();
      Iterator mit = methods.iterator();
      while(!found && mit.hasNext()) {
        Method m = (Method)mit.next();
        if (m.getParameterTypes()[0].isInstance(o)) {
            try {
              m.invoke(task, new Object[] { o });
            } catch(IllegalAccessException ex) {
              // we were only looking for public methods anyway
              throw new IllegalStateException();
            } catch(InvocationTargetException ex) {
              // The only exceptions we allow to be thrown from
              // execute are RuntimeException subclases
              throw (RuntimeException)ex.getTargetException();
            }
            found = true;
        }
      }
      if (!found)
        throw new IllegalArgumentException(
          "No handler found for object type " +
            o.getClass().getName());
    }
  }
}

Instead of having that ugly while loop, we can now pass an object to the VisitingIterator and the correct execute(...) method is called for each element in the collection. The OldVisitingIterator now becomes:

import java.util.*;

public class VisitingIteratorTest {
  public static void main(String[] args) {
    Collection c = new LinkedList();
    for (int i=0; i<3; i++) c.add(new Integer(i));
    VisitingIterator vit = new VisitingIterator();

    vit.visit(c, new Object() {
      public void execute(Integer i) {
        System.out.println(i.intValue() + 10);
      }
    });

    c.add(new Float(2.1));
    c.add("Hello");

    vit.visit(c, new Object() {
      public void execute(Object o) {
        System.out.println(o);
      }
      public void execute(Number n) {
        System.out.println(n.intValue() + 20);
      }
      public void execute(Integer i) {
        System.out.println(i.intValue() + 10);
      }
      public void execute(String s) {
        System.out.println(s.toLowerCase());
      }
    });

    vit.visit(c, new Object() {
      public void execute(Integer i) {
        System.out.println(i.intValue() + 10);
      }
    });
  }
}

The output from our new style is:

10
11
12
10
11
12
22
hello
10
11
12
Exception in thread "main" java.lang.IllegalArgumentException:
No handler found for object type java.lang.Float
        at VisitingIterator.visit(VisitingIterator.java:62)
        at VisitingIteratorTest.main(VisitingIteratorTest.java:33)

Perhaps I've been smoking Java for too long, but I much prefer that code to the while(it.hasNext()) ... but I have not had the chance to try this idea out "in the real world". I will start using it and let you know if it makes code neater (or not). I know that it will be less efficient, but then, Java is so slow anyway, I'd rather have cool style than super-optimal code.

Until next week ...

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...