slanted W3C logo

Day 32 — Iterator; Exceptions

Recap

If you have a Collection (List or Set) you can use a for-each loop to visit every element in the collection. For example, you can sum the values of all of the elements in a Set:

      /*
      Set<Double> someNumbers = ...
      */
      
      double sum = 0.0;
      for (Double d : someNumbers)
      {
         sum += d;
      }
      double average = sum / someNumbers.size();

The code fragment above has a potential problem; do you see it?

Iterators

An Iterator is an object that lets the client traverse a collection. Iterator is actually an interface:

public interface Iterator<E> 
{
    boolean hasNext();
    E next();
    void remove(); //optional
}

Every class that implements Collection has a method named iterator that returns an iterator for the elements in the collection.

Iterator Traversal

Traversing a collection using an iterator uses a method similar to the chained traversal method. Suppose you have collection of elements that we can label e0, e1, and so on.


Note that the order of the elements is determined by the container.

Iterator Traversal: Step 1

The first thing we need to do is to get an iterator for the collection by calling the collection's iterator method. This creates an iterator object that points not at the first element e0, but at a position immediately before e0.


      /*
      Set<Double> someNumbers = ...
      */
      
      double sum = 0.0;
      Iterator<Double> iter = someNumbers.iterator();

Iterator Traversal: Step 2a

Inside the loop, you use the iterator to access the current element in the traversal using the method next. next returns the next element in the traversal, and throws an exception if there are no more elements in the traversal.


      double sum = 0.0;
      Iterator<Double> iter = someNumbers.iterator();
      for (???)
      {
         sum = sum + iter.next();
      }

Iterator Traversal: Step 2b

Invoking next has the side-effect of moving the iterator to a position just before the next element in the collection.


Iterator Traversal: Step 3

The loop body should execute only if next will succeed (not throw an exception). The iterator method hasNext returns true if next will succeed, and false otherwise.

      double sum = 0.0;
      Iterator<Double> iter = someNumbers.iterator();
      for ( ; iter.hasNext(); )
      {
         sum = sum + iter.next();
      }

You can also use a while loop:

      double sum = 0.0;
      Iterator<Double> iter = someNumbers.iterator();
      while ( iter.hasNext() )
      {
         sum = sum + iter.next();
      }

Iterator Traversal: Step 4

Eventually, the loop will cause the iterator to move past the last element. At this point, hasNext will return false and the loop will stop.


Why Iterators?

For-each loops are really iterator loops that the compiler hides from the programmer.

For-each loops are the preferred method of traversing a collection, but it has limitations. One limitation is that you cannot remove elements from a collection using a for-each loop.

Suppose you want to remove all the numbers greater than some value from a collection (of numbers). You might try something using a for-each loop like this:

import java.util.*;

public class ForEachFail
{
   public static void main(String[] args)
   {
      // Create list of numbers from 0-9
      List numbers = new ArrayList<Double>();
      for (int i = 0; i < 10; i++)
      {
         numbers.add((double)i);
      }
      
      // Remove 5-9 ?
      final double THRESHOLD = 5.0;
      for (Double d : numbers)
      {
         if (d >= THRESHOLD)
         {
            numbers.remove(d);
         }
      }
   }
}

If you compile and run the program you will find that a ConcurrentModificationException exception is thrown. If a collection is modified while an iterator is visiting the collection, the iterator will probably throw a ConcurrentModificationException the next time next is invoked.

Using Iterator remove

The only safe way to remove an element from a collection during a traversal is to use an iterator and invoke its remove method:

      // Remove 5-9
      final double THRESHOLD = 5.0;
      Iterator<Double> iter = numbers.iterator();
      for (; iter.hasNext(); )
      {
         if (iter.next() >= THRESHOLD)
         {
            // Remove the element previously
            // returned by next
            iter.remove();
         }
      }
      System.out.println(numbers);

Note that you can only invoke remove once each time you invoke next.

Exercise 10.13

Ask the user for 20 or fewer unique integers and compute the median of the integers. Your program should:

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

public class Median
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      Scanner input = new Scanner(System.in);
      
      output.println("Enter a list of unique integers separated by spaces");
      String sInput = input.nextLine();
      
      StringTokenizer tok = new StringTokenizer(sInput);
      Set<Integer> numbers = new TreeSet<Integer>();
      while (tok.hasMoreTokens())
      {
         int num = Integer.parseInt(tok.nextToken());
         if (!numbers.add(num))
         {
            output.printf("%d was not unique%n", num);
         }
      }
      
      final int SMALLER = numbers.size() / 2;
      int median = 0;
      for (Integer i : numbers)
      {
         int count = 0;
         for (Integer j : numbers)
         {
            if (j < i)
            {
               count++;
            }
         }
         if (count == SMALLER)
         {
            median = i;
         }
      }
      output.printf("median of %s is %d%n", numbers.toString(), median);
   }
}

Exceptions

A successfully compiled program can still fail when it is run. Hopefully, such failures are rare occurrences.

The term exception is shorthand for the phrase "exceptional event." An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

The programmer can cause errors to occur by violating preconditions or making logic errors:

      String s = "hello";
      String t = substring(6);  // IndexOutOfBoundsException
      /*
      List<Integer> someIntegers ...
      */
      int sum = 0;
      for (Integer i : someIntegers)
      {
         sum += i;
      }
      // ArithmeticException is someIntegers is empty
      int average = sum / someIntegers.size();

The user can cause errors to occur by entering invalid input:

Enter a number: abc

If the program uses Integer.parseInt to convert the string to an integer a NumberFormatException will occur.

The runtime environment that the program executes in can cause errors to occur.

      // FileNotFoundException if a file named
      // filename is not readable by the user
      Scanner fileInput =
         new Scanner(new File(filename));

Similarly, if you try to write a file to a full hard drive an IOException will occur.

Exception Terminology

When the virtual machine detects an invalid operation, it throws an exception. Throwing an exception involves creating an exception object that contains information about the error. The exception object is then handed to the Java runtime system.

When the runtime system receives an exception object, it searches for a block of code called an exception handler that can handle the exception. If such a block of code is found, the exception handler is said to catch the exception.

Thus far, you have not written any exception handlers. If the Java runtime system cannot find an exception handler, the program terminates in an uncontrolled fashion (it crashes).

A Simple Example

In Java, you use a try statement to catch the exception, and the try must provide a handler to deal with the exception.

      int num = 0;
      try
      {
         num = Integer.parseInt(someString);
      }
      catch (NumberFormatException ex)
      {
         output.println("not a number");
      }

To Do For Next Lecture

Finish reading Chapter 11.