Skip to main content

Section 10.5 Examples of Interfaces

Subsection 10.5.1 Iterators and the Iterable Interface

Let’s take a look at a standard interface that expresses exactly this idea. This is part of the Java standard library, and its called Iterable. It is implemented by numerous classes, all having in common that they have some notion of going over a list of elements. This interface allows us to write a foreach loop. For example whenever we write:
for (Grade g : grades) {
  // do something with g
}
Then what we really require of the variable grades is that it holds a value of type Iterable. This may be an ArrayList, or it could be something completely different, as long as it implements Iterable. We will in fact create something somewhat different soon. But first, what is this Iterable interface? Here is what its definition might look like, simplifying a lot:
interface Iterable<T> {
  Iterator<T>	iterator();
  // other stuff we don't care about yet
}
So an Iterable interface is very simple. It has a parametric type T that we need to specify, namely what kinds of elements we are getting in the iteration (Grade in the loop above), and it just has a method called iterator() that returns an Iterator<T> object. What is Iterator<T> you ask? Well I’m glad you did!
interface Iterator<T> {
  boolean	hasNext();
  T	next();
}
An iterator has two methods. The hasNext method lets you know if there are more elements that your iterator can provide. While the next method provides that next element. An iterator comes with an important "contract": You should only call next if you have just called hasNext and the answer was true. Otherwise the results are unpredictable.
This is a good time to bring something up, because it is exactly the distinction between interface and behavior that we discussed earlier. Implementations of the Iterator interface are expected to follow this contract, and every user of the Iterator interface is expected to call the Iterator methods only in accordance to this contract. However these behaviors are not something that the compiler will enforce: You can technically implement this interface without obeying the contract, and that will only lead to subtle problems down the line.
However we tend to abuse this terminology, so it is worth mentioning:
When we talk about interface we typically mean not just the "Java Interface" part of a list of methods with certain signatures, but also the behavior of those methods and the protocol for their use, which goes beyond what the compiler can check.
Back to our example, behind the scenes Java turns our foreach loop, which looked like this:
for (Grade g : grades) {
  // do something with g
}
to something involving an iterator:
Iterator<Grade> it = grades.iterator();
while (it.hasNext()) {
  Grade g = it.next();
  // do something with g
}
I hope you will agree the foreach loop is nicer. But it is important to keep in mind what it really corresponds to, as we are about to create our own examples of iterators.
A foreach loop is just a while loop with an iterator
Let’s take a look at some custom iterables. We will define a Range class, for sequence: You give it a start integer a and an end integer b and it represents all numbers form a up to and including b.
public class Range {
  int a;
  int b;

  Range(int a, int b) {
    this.a = a;
    this.b = b;
  }
}
Well on its own that’s not a very interesting range. But let’s make it iterable. In order to achieve that, let’s first create a "RangeIterator", which is an inner class of Range, so we would add this near the end of the definition of Range:
private class RangeIterator implements Iterator<Integer> {
  private int index;

  RangeIterator() { this.index = a; }

  public boolean hasNext() { return index <= b; }

  public Integer next() {
    int toReturn = index;
    index += 1;
    return toReturn;
  }
}
I am using something important here: This is an inner class, defined within another class, and it is not static. This means that there is essentially a different version of this class for each Range object. As such, the methods of this inner class have access to the variables of that Range object, which is why writing a and b works even though they are not fields of the RangeIterator class. This is a feature that is not used often, but which makes sense for these iterators.
Inner classes that are not static are specific to each object and can access its fields directly.
Those seasoned C and C++ developers out there might choose to implement next simply as return this.index++; and perhaps in another life I might have agreed with you. But that is a bit more confusing and relies on a careful understanding of the mechanics of the post-increment operator ++. Understanding code is hard enough without us making it harder still. So I opted for a longer version.
Now all we have to do is make the Range class implement Iterable:
public class Range implements Iterable<Integer> {
  ...
  public Iterator<Integer> iterator() {
    return new RangeIterator();
  }
And that’s it! We can use it in a method with something like this:
Range range = new Range(1, 20);
for (Integer i : range) {
  System.out.println(i);
}
This form of a "for loop" should be familiar to those coming from Python.

Subsection 10.5.2 Iterable example: Reading lines

As another example, let’s write an iterable that reads from a file. I’ll call the class FileReader. Here’s how it might look like:
public class FileReader implements Iterable<String> {
  String filename;

  FileReader(String filename) { this.filename = filename; }

  public Iterator<String> iterator() { return new FileReaderIterator(); }

  private class FileReaderIterator implements Iterator<String> {
    private final Scanner scanner;

    public FileReaderIterator() {
      File file = new File(filename);
      // Not quite right, needs some error-checking
      scanner = new Scanner(file);
    }

    public boolean hasNext() { return scanner.hasNextLine(); }
    public String next() { return scanner.nextLine(); }
  }
}
So this class takes a filename, and when asked for an iterator it returns a FileReaderIterator. This in turn opens up the file, attaches it to a scanner, then uses that scanner to see if there are any lines on the file. Then it returns each new line. Here is a run:
FileReader fr = new FileReader("....");
for (String line : fr) {
  // do something with line
}
We could go on, but this is probably a good place to stop. The main point is this:
The Iterable and Iterator interfaces provide a basic infrastructure for implementing "iterate over some items" functionality, and allow their users to rely on just that functionality, oblivious of the actual implementation under the hood.
This is in general what interfaces allow us to do: Many different classes can provide the same functionality in different ways, and the users don’t need to care about those ways. Interfaces separate prescribed behavior from the implementation of that behavior.

Subsection 10.5.3 Standard Library Interfaces

The standard Java libraries provide a number of built-in interfaces that you may run into, so it would be good to be a bit familiar with at least some basic interfaces.
  • We already saw Iterable and Iterator earlier.
  • Two important interfaces are Collection and List. They represent having a collection of items, to which you can add or remove items, and also of course iterate over the items. The List interface extends that by adding the extra specification of an ordering to the elements, so you can for example insert an element at the location with index 3. The ArrayList class is a standard implementation of both interfaces.
  • The Map interface is another important interface, representing key-value dictionary/map functionality. You can store a key-value pair, get the value stored at a certain key, find out if a key exists, remove a key and its associated value, etc. Its standard implementation is the HashMap class.