Running on Java 24-ea+21-2447 (Preview)
Home of The JavaSpecialists' Newsletter

103New for in Loop Gymnastics

Author: Dr. Heinz M. KabutzDate: 2005-02-07Java Version: 5Category: Language
 

Abstract: Java 5 introduced the Iterable interface, used by the enhanced "for" statement to iterate. Collection extends Iterable, but we can also write our own classes that implement it. This way we can use the enhanced "for" statement to iterate over them. Examples would be ResultSet and InputStream.

 

Welcome to the 103rd edition of The Java(tm) Specialists' Newsletter. After a weekend at home, I am back in Johannesburg, continuing with the Java 5 Delta Course - Letting the Tiger out of the cage.

This morning, I read with great sadness in the Cape Times that my Applied Mathematics lecturer Dr Brian Hahn at the University of Cape Town passed away after being brutally assaulted by a student. I attended Dr Hahn's lectures in 1990, and besides being a great lecturer, I was also impressed by his boldness in publicly declaring his faith. This is a great loss for Cape Town, one of the most beautiful cities in the world. So, here is a salute to Dr Hahn - thanks for your hard work, and strength to your family in this difficult time! [Oh, just to add fuel to the fire, the suspect was released on bail of US$ 83]

And now, let us get our teeth into the new for/in construct of Java.

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

New for/in Loop Gymnastics

The new for/in loop gives us the ability to iterate through any object that implements the Iterable interface. In this newsletter, I will show some applications of Iterable.

Result Set Iterable

The most obvious place of iteration that I immediately thought of was the ResultSet. Currently, this does not implement the Iterable interface, but you can add this using an Object Adapter. This class is a bit tricky, since the columns could each have different types. My solution returns a String[] of the columns for each row.

import java.sql.*;
import java.util.*;

/**
 * This class allows you to iterate over a ResultSet using the
 * standard for/in construct.  It always returns String[] for
 * each row, irrespective of the real types of the objects.
 *
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class ResultSetIterable implements Iterable<String[]> {
  private final ResultSet rs;
  private final int columns;

  public ResultSetIterable(ResultSet rs) throws SQLException {
    this.rs = rs;
    columns = rs.getMetaData().getColumnCount();
  }

  public Iterator<String[]> iterator() {
    return new Iterator<String[]>() {
      private boolean nextCalled = true;
      private boolean moreObjects;
      public boolean hasNext() {
        if (nextCalled) {
          try {
            moreObjects = rs.next();
            nextCalled = false;
          } catch (SQLException e) {
            throw new RuntimeException(e);
          }
        }
        return moreObjects;
      }

      public String[] next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        try {
          String[] values = new String[columns];
          for (int i = 0; i < values.length; i++) {
            values[i] = rs.getString(i + 1);
          }
          nextCalled = true;
          return values;
        } catch (SQLException e) {
          throw new RuntimeException(e);
        }
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }
}

We can then create the ResultSetIterable from any ResultSet and iterate through it with the new for/in loop, or use the standard Iterator. This approach is better than copying everything into a Collection and returning a handle to its Iterator, since with a large ResultSet, that could cause an OutOfMemoryError.

Here is a short example. You will have to replace the URL and driver Strings with your own:

import java.sql.*;
import java.util.Arrays;

/**
 * Here I am connecting to a database table and selecting all
 * rows, then using the new for/in construct to iterate through
 * the ResultSet.
 *
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class ResultSetIterableTest {
  public static void main(String[] args) throws Exception {
    // you have to set up your JDBC connection yourself
    Class.forName("...");
    Connection con = DriverManager.getConnection("...");
    Statement st = con.createStatement();
    ResultSet rs = st.executeQuery("SELECT * FROM customers");
    for (String[] row : new ResultSetIterable(rs)) {
      // Arrays.toString() is a new Tiger function
      System.out.println(Arrays.toString(row));
    }
  }
}

I was impressed that this was so easy to do. We now have a consistent approach to iteration :)

Iterable Input Streams and Readers

An idea that I learnt from the O'Reilly book on Java 5.0 Tiger: A Developer's Notebook by Brett McLaughlin and David Flanagan [ISBN 0596007388] (excellent book btw, it will teach you many of the practical applications of Tiger), is to read Strings from a file, or in our case, from a stream of characters.

In this code, I am reading either from an InputStream of from a Reader, one line at a time. When we construct the InputParser, we specify what objects must be returned with the Iterator. However, there is a slight problem. The objects that are read are Strings, so how do we convert from those to the objects that we want to return? Very easily, we simply define an interface Parser<T> that takes a String and converts it to your object. You need to implement that, but I have provided some default Parsers in the InputParser class.

The rest of the code is fairly self-explanatory. With the Iterator, I return true if there are more lines in the stream. One easier approach would be to read all the lines into a collection, and then return an iterator over that. However, the disadvantage is that you have to read all the lines into memory. If you are reading a large file, you might run out of memory.

import java.io.*;
import java.util.*;

/**
 * I thought for a while for a good name for this class.  This
 * was the best I could come up with, but I am not 100% happy
 * with it either.
 *
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class InputParser <T> implements Iterable<T> {
  private final BufferedReader reader;
  private final Parser<? extends T> parser;

  public InputParser(Reader in, Parser<? extends T> parser) {
    this.parser = parser;
    reader = new BufferedReader(in);
  }

  public InputParser(InputStream in, Parser<? extends T> parser) {
    this(new InputStreamReader(in), parser);
  }

  public Iterator<T> iterator() {
    return new Iterator<T>() {
      private String nextLine;
      private boolean lineReadFromFile = false;

      public boolean hasNext() {
        if (!lineReadFromFile) {
          try {
            nextLine = reader.readLine();
          } catch (IOException e) {
            return false;
          }
          lineReadFromFile = true;
        }
        return nextLine != null;
      }

      public T next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }
        lineReadFromFile = false;
        return parser.convert(nextLine);
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  /**
   * You use this interface to convert a String to your type of
   * object T.  I prefer defining interfaces closely to where
   * they will be used, hence the inner interface Parser.
   */
  public interface Parser<T> {
    T convert(String s);
  }

  /**
   * The default implementation simply returns the strings.
   */
  public static final Parser<String> STRING_PARSER =
      new Parser<String>() {
        public String convert(String s) {
          return s;
        }
      };

  public static final Parser<Double> DOUBLE_PARSER =
      new Parser<Double>() {
        public Double convert(String s) {
          return Double.parseDouble(s);
        }
      };

  public static final Parser<Integer> INT_PARSER =
      new Parser<Integer>() {
        public Integer convert(String s) {
          return Integer.parseInt(s.replaceAll("\\..*", ""));
        }
      };

  /**
   * A simple CSVParser that should probably be expanded to
   * handle delimited separators, e.g. \,
   */
  public static final Parser<String[]> CSV_PARSER =
      new Parser<String[]>() {
        public String[] convert(String s) {
          List<String> result = new ArrayList<String>();
          StringTokenizer st = new StringTokenizer(s, ",");
          while (st.hasMoreTokens()) {
            result.add(st.nextToken());
          }
          return result.toArray(new String[0]);
        }
      };
}

We can now read in numbers from any input stream, such as a file, or a database, or a TCP/IP stream, and iterate through them with the new for/in construct. For example:

import java.io.*;

/**
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class NumberParserTest {
  public static void main(String[] args) {
    // Create a byte array that contains 10 random numbers less
    // than 10000.
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(bout);
    for (int i = 0; i < 10; i++) {
      out.println(Math.random() * 10000);
    }
    out.close();
    byte[] data = bout.toByteArray();

    // Read the Strings in and convert them to doubles.
    InputParser<Double> doubleFile = new InputParser<Double>(
        new ByteArrayInputStream(data), InputParser.DOUBLE_PARSER);
    double total = 0;
    for (double d : doubleFile) { // this uses autoboxing
      total += d;
    }
    System.out.println(total);

    InputParser<Number> numberFile = new InputParser<Number>(
        new ByteArrayInputStream(data), InputParser.INT_PARSER);
    for (Number i : numberFile) {
      System.out.println(i);
    }
  }
}

You can therefore iterate through InputParsers and read the type that you have specified. On my run, it output the following:

    37817.79477180377
    4572
    5802
    6213
    3851
    2402
    5578
    3894
    263
    13
    5225

Parsing CSV to Generate Objects

Sometimes we need to read the construction parameters of objects from a CSV file, so I have provided a default CSVParser that converts a String to a String[]. Let us use that together with a Person class:

/**
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class Person {
  private final String firstName;
  private final String surname;
  private final int age;

  public Person(String firstName, String surname, int age) {
    this.firstName = firstName;
    this.surname = surname;
    this.age = age;
  }

  @Override // always use this when you are overriding
  public String toString() {
    return firstName + " " + surname + ", " + age;
  }
}

To generate a Person object from a String, we use the CSVParser from the InputParser:

/**
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class PersonParser implements InputParser.Parser<Person> {
  public Person convert(String s) {
    String[] values = InputParser.CSV_PARSER.convert(s);
    String firstName = values[0];
    String surname = values[1];
    int age = Integer.parseInt(values[2]);
    return new Person(firstName, surname, age);
  }
}

And we can then use that PersonParser class to read in a CSV file, and generate Person objects using the new for/in loop:

import java.io.*;

/**
 * @author Heinz Kabutz
 * @since 2005/02/07
 */
public class PersonParserTest {
  private static final String FILE = "persons.txt";
  public static void main(String[] args) throws IOException {
    PrintWriter out = new PrintWriter(new FileWriter(FILE));
    out.println("Heinz,Kabutz,33");
    out.println("John,Green,33");
    out.println("Anton,de Swardt,33");
    out.println("Zachary,Thalla,32");
    out.close();

    InputParser<Person> personFile = new InputParser<Person>(
        new FileInputStream(FILE), new PersonParser());
    for (Person person : personFile) {
      System.out.println(person);
    }
  }
}

Now, when we run this code, we end up with this output:

    Heinz Kabutz, 33
    John Green, 33
    Anton de Swardt, 33
    Zachary Thalla, 32

Kind regards

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...