Iterators And Generics

Example Iterable Class

We want a collection class Repeater that supports the following behavior:


    // Create a collection with 5 "Hello"s in it:
    Repeater repeats = new Repeater("Hello", 5);
    
    for (String word : repeats) { // Print the five "Hello"s
      System.out.println(word);
    }

Repeater Implementation

It would be silly to explicity store five copies of “Hello”. Here is a reasonable implementation:

public class Repeater implements Iterable<String> {

  private String what;
  private int howMany;

  public Repeater(String what, int howMany) {
    this.what = what;
    this.howMany = howMany;
  }

  @Override
  public Iterator<String> iterator() {
    return new RepeatIterator();
  }

  private class RepeatIterator implements Iterator<String> {

    int remaining = howMany; // Notice that we can access the members
                             // of the outer class.

    @Override
    public boolean hasNext() {
      return remaining > 0;
    }

    @Override
    public String next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }

      remaining--;
      return what;
    }

  }

}

Adding a remove Method

public class Repeater implements Iterable<String> {

  private String what;
  private int howMany;

  public Repeater(String what, int howMany) {
    this.what = what;
    this.howMany = howMany;
  }

  @Override
  public Iterator<String> iterator() {
    return new RepeatIterator();
  }

  private class RepeatIterator implements Iterator<String> {

    int remaining = howMany;
    boolean deleteOk = false;

    @Override
    public boolean hasNext() {
      return remaining > 0;
    }

    @Override
    public String next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }

      remaining--;
      deleteOk = true;
      return what;
    }

    @Override
    public void remove() {
    
      // remove can only be called once after each call to next.
      // any other calls must result in an exception.
      if (!deleteOk) {
        throw new IllegalStateException();
      }
      howMany--;
      deleteOk = false;
    }

  }

}

Generic Repeater

There is no reason to restrict the implementation to only work with strings.

public class Repeater<T> implements Iterable<T> {

  private T what;
  private int howMany;

  public Repeater(T what, int howMany) {
    this.what = what;
    this.howMany = howMany;
  }

  @Override
  public Iterator<T> iterator() {
    return new RepeatIterator();
  }

  // Notice that RepeatIterator doesn't need its own type parameter.
  // We would never need to specify its type independently from
  // the type provided to Repeater.
  private class RepeatIterator implements Iterator<T> {

    int remaining = howMany;
    boolean deleteOk = false;

    @Override
    public boolean hasNext() {
      return remaining > 0;
    }

    @Override
    public T next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }

      remaining--;
      deleteOk = true;
      return what;
    }

    @Override
    public void remove() {
      if (!deleteOk) {
        throw new IllegalStateException();
      }
      howMany--;
      deleteOk = false;
    }

  }

}

Anonymous Inner Classes

Anonymous inner classes are often used in cases where we have a single-use class. This is generally the case for Iterator classes:

public class Repeater<T> implements Iterable<T> {

  private T what;
  private int howMany;

  public Repeater(T what, int howMany) {
    this.what = what;
    this.howMany = howMany;
  }

  @Override
  public Iterator<T> iterator() {
    return new Iterator<T>() { // Body of the class goes here. It has no name.
      int remaining = howMany; // Anonymous classes can't have constructors.
      boolean deleteOk = false;

      @Override
      public boolean hasNext() {
        return remaining > 0;
      }

      @Override
      public T next() {
        if (!hasNext()) {
          throw new NoSuchElementException();
        }

        remaining--;
        deleteOk = true;
        return what;
      }

      @Override
      public void remove() {
        if (!deleteOk) {
          throw new IllegalStateException();
        }
        howMany--;
        deleteOk = false;
      }
    };

  }

}

Generic Methods

Type parameters are usually provided when an object is instantiated. When static methods are called, there is no object, so alternative syntax is required:

public class GenericsDemo {
  
  public static <E> void copy(ArrayList<E> from, ArrayList<E> to) {
    for (E element : from) {
      to.add(element);
    }
  }
  
  public static void main(String[] args) {
    ArrayList<String> a = new ArrayList<>();
    ArrayList<String> b = new ArrayList<>();
    ArrayList<Integer> c = new ArrayList<>();
    
    //... 
    
    GenericsDemo.<String>copy(a, b); // OK!
    GenericsDemo.copy(a, b);         // OK! (type will be inferred)
    copy(a, c);                      // Will not compile. 
    

  }

}