Interfaces

Here we look at "interfaces" in Java, which is essentially a weak form of allowing one class to extend multiple base classes.

A Simple Motivating Example

Did you know that Java comes with pre-built sorting methods? It does! There is a static method in the Arrays class called sort. It's a great method that takes an array of your objects and sorts them based on their "natural ordering". Well let's imagine we made our own class called Cat and want to sort an array of these cats:

public class Cat extends Mammal {
  private String color;
  private int health;
  public String noise() { return "meow"; }
}

What do you think the "natural ordering" is? How can a sort method possibly know how to sort these guys? Alphabetically by color? Numerically by health? This should seem odd to you, so of course you need to implement some sort of method that the sort method expects! This is where interfaces come in. Note that a sort method shouldn't care if you are sorting Cats, Trees, Integers, Waffles, or MoldyCheese. All it cares is that each of these types is comparable in some sort of natural ordering. Interfaces to the rescue!

An Interface defines exactly that: an interface but with no implementation. It defines the names and parameters of methods that are expected to be implemented, and any class that says they implement the interface...well, they better implement the interface! Our Cat class slightly changes:

Comparable.java
public interface Comparable {
  public int compareTo(Object);
}
Cat.java
public class Cat extends Mammal implements Comparable {
  private String color;
  private int health;
  public String noise() { return "meow"; }

  // The one method that a Comparable interface requires.
  // You return a 1 if you're 'greater' than the given cat,
  // 0 if equal, -1 if 'less'.
  public int compareTo(Object o) {
    Cat other = (Cat)o;
    if( health > other.getHealth() ) { return 1; }
    else if( health == other.getHealth() ) { return 0; }
    else { return -1; }
}

Hopefully you see that interfaces are very similar to abstract classes. They list methods that are required to be implemented. More examples and details now follow:

A Complex Motivating Example

This is a bit of a complicated motivating example, but here it goes. First of all, here's a simple calculator app. It acts like a simple hand-held calculator, where you give one number or operator per line (like a single button push). You have the operators +,-,/,* and =, as well as c to clear and q to quit.

SimpleCalc.java
import java.util.*;

public class SimpleCalc {
  public static void main(String[] args) {
    Calculator c  = new Calculator();
    Scanner    sc = new Scanner(System.in);
    String     s;

    while( !(s = sc.nextLine()).equals("q") ) {
      try {
        String r = c.process(s);

        if( r != null )
          System.out.println(r);
      } catch( CalculatorException e ) {
        System.out.println(e.getMessage());
      }
    }
  }
}
CalculatorException.java
public class CalculatorException extends Exception {
  public CalculatorException(String msg) {
    super(msg);
  }
}
Calculator.java
public class Calculator {
  public String process(String s) throws CalculatorException {
    if( s.equals("c") ) {
      vsi             = -1;
      expectingNumber = true;
      return null;
    }

    if( expectingNumber ) {
      try {
        double x = Double.parseDouble(s);
        vs[++vsi] = x;

        if( (vsi > 1) && (vs[vsi - 1] == (int)'*') ) {
          vs[vsi -= 2] *= x;
        } else if( (vsi > 1) && (vs[vsi - 1] == (int)'/') ) {
          if( x == 0.0 )
            throw new CalculatorException("Attempted divide by zero!");
          vs[vsi -= 2] /= x;
        }
        expectingNumber = false;
      } catch( CalculatorException e ) {
        throw new CalculatorException("Attempted divide by zero!");
      } catch( Exception e ) {
        throw new CalculatorException("Expected a number or c, got '" + s + "'!");
      }
    } else {
      if( s.equals("=") ) {
        sumSquash();
        return "" + vs[0];
      } else if( s.equals("+") ) {
        sumSquash();
        vs[vsi = 1] = (int)'+';
      } else if( s.equals("-") ) {
        sumSquash();
        vs[vsi = 1] = (int)'-';
      } else if( s.equals("*") ) {
        vs[++vsi] = (int)'*';
      } else if( s.equals("/") ) {
        vs[++vsi] = (int)'/';
      } else {
        throw new CalculatorException("Expected *,+,= or c, got '" + s + "'!");
      }
      expectingNumber = true;
    }
    return null;
  }

  private double[] vs = { 0, 0, 0, 0, 0, 0 };
  private int vsi = -1;
  private boolean expectingNumber = true;

  public String toString() {
    String s = "";

    for( int i = 0; i <= vsi; ++i ) {
      s += " " + vs[i];
    }
    return s;
  }

  private void sumSquash() {
    for( int i = 2; i <= vsi; i += 2 ) {
      vs[0] +=
        (vs[i - 1] == (int)'+' ? 1 : -1) * vs[i];
    }
    vsi = 0;
  }
}

Here's a sample run:

$ java SimpleCalc
4
+
5
*
2
=
14.0
c
q

Now, completely separately, I have developed a system for "logging" things that happen in a program. This turns out to be a very important task, although normally for larger programs. In SY110 you may remember the importance of such logs for web-server programs. This is a super-junior system. It just logs the time (in milliseconds) for each event, and allows you to query log events by a range of times.

Logger.java
import java.util.*;

public class Logger {
  long tbase = System.currentTimeMillis();

  public void addLogEntry(Loggable e) {
    entries.add(e);
  }

  public void printLog(long start, long end) {
    for( Loggable e : entries ) {
      if( (start <= e.getLogTimeInMillis()) && (e.getLogTimeInMillis() <= end) ) {
        System.out.println((e.getLogTimeInMillis() - tbase) / 1000.0 + "\t"
                           + e.getLogMessage());
      }
    }
  }

  ArrayList<Loggable> entries = new ArrayList<Loggable>();
}
Loggable.java
public abstract class Loggable {
  public abstract long   getLogTimeInMillis();
  public abstract String getLogMessage();
}

Forget about how the Logger methods work. What I want you to focus on is that we have an abstract class Loggable, and Objects that we want to be logged by this system are expected to extend the Loggable class. This is a common way to architect something like this.

Interfaces vs. multiple inheritance

So now, suppose I want to log the CalculatorExceptions that come up during the run of SimpleCalc. That's natural enough. Then I print out a summary error log when I'm done - either to the screen or to a file. The problem is this: to use CalculatorException with Java's try-catch-throw we need to have it extend Throwable (or a descendent), but to use CalculatorException with Logger it needs to extend Loggable. Can we do both?

This is an interesting question. Some languages (like C++) allow a class to extend two classes simultaneously. This is called multiple inheritance. Java does not. However Java does allow a limited form of it. In Java, you can create what's called an Interface, which is an abstract class in which every method is pure abstract (i.e. no definitions), and while a class can only extend one base class, it can extend any number of interfaces — although the keyword is implements rather than extends when it comes to interfaces.

Back to the example ...

So, looking back to our example, we can change Loggable from a class to an interface — after all, all the methods were pure abstract anyway. Then we can define CalculatorException as both extending Exception and implementing the interface Loggable. This way, it will be able to fill both roles!

Loggable.java
public interface Loggable {
  public abstract long   getLogTimeInMillis();
  public abstract String getLogMessage();
}
CalculatorException.java
public class CalculatorException extends Exception implements Loggable {
  long t;
  public CalculatorException(String msg) {
    super(msg);
    this.t = System.currentTimeMillis();
  }

  public long getLogTimeInMillis() {
    return t;
  }

  public String getLogMessage() {
    return getMessage();
  }
}

Finally, this gives us a SimpleCalc program that does everything we want in this example.

SimpleCalc.java
import java.util.*;

public class SimpleCalc {
  public static void main(String[] args) {
    Logger log    = new Logger();
    Calculator c  = new Calculator();
    Scanner    sc = new Scanner(System.in);
    String     s;

    while( !(s = sc.nextLine()).equals("q") ) {
      try {
        String r = c.process(s);

        if( r != null ) {
          System.out.println(r);
        }
      } catch( CalculatorException e ) {
        System.out.println(e.getMessage());
        log.addLogEntry(e);
      }
    }
    System.out.println("Error Log");
    log.printLog(0, System.currentTimeMillis());
  }
}

Here's a sample run:

~/$ java SimpleCalc
 2
 /
 0
Attempted divide by zero!
 1
 +
 *
Expected a number or c, got '*'!
 3
 =
5.0
 x
Expected *,+,= or c, got 'x'!
 q
6.154  Attempted divide by zero!
17.956  Expected a number or c, got '*'!
26.47  Expected *,+,= or c, got 'quit'!

Implementing an Interface vs. Extending a Class

It's natural to be confused about the two very similar ideas of "extending a class" as opposed to "implementing and interface". So what's going on with the two? Well, remember that two of the ways we identified that procedural programming was insufficient with respect to separating interface vs. implementation were that:

Well, true inheritance (extends) gives us a mechanism for doing both. Java Interfaces give us only the second. So if you need the first, or both first and second, think inheritance. When you only need the second, think implementing interfaces.

Some people like to think of an interface as a contract. They think "if a class implements this interface, then I promise that the code that I've put together that relies on that interface will work". In your code, you can use an interface just as you do a base class: you define references to the interface as if it was a class type, and these references can point to any object whose actual type is a class that implments the interface.

Interfaces: One killer app — Comparable

Interfaces give us a mechanism for solving a problem that (I hope) was a source of consternation in IC210: how to write selection sort just once and be able to use it with different types of objects. You will recall that we made a big deal in IC210 that sorting is the same no matter what kind of objects are in your array and no matter what criterion you use for determining the order. The ordering criterion was packaged up in a function we called "before" and the selectionSort function just called called it. When we changed from sorting one type (strings, for example) to another (points, for example) we made a copy of the selection sort code, and we went through and replaced each occurrence of string with point. What we couldn't do is to write selectionSort once, and use it with different types. Interfaces will allow us to do that!

As long as we can compare pairs of objects of a given type, we can sort them. So our starting point is an interface "Comparable" that captures this one operation:

public interface Comparable {
  public int compareTo(Object o);
}
In fact, this already exists in the Java API, so we don't need to declare it ourselves. Now I can write the selectionSort code with no assumptions about the types of the objects in the array except that they implement the Comparable interface:

MySort.java
public class MySort {
  public static void sort(Comparable[] A) {
    for( int L = A.length; L > 1; --L ) {
      int m = 0;

      for( int i = 1; i < L; ++i ) {
        if( A[m].compareTo(A[i]) < 0 ) {
          m = i;
        }
      }
      Comparable t = A[L - 1];
      A[L - 1] = A[m];
      A[m]     = t;
    }
  }
}

The fact that only assume that the array elements implement Comperable is manifested in the code: the only method that gets called on an array element in this code is compareTo(). When you declare a reference as type Comparable, that's all you can do! Now let's see a nice example of using this. We'll use our good friend the Point class, and make it implement compareTo(), where closeness to the origin defines the sorted order. We'll also make use of the fact that the String class in Java implements Comparable.

Ex3.java
import java.util.*;

public class Ex3 {
  public static void main(String[] args) {
    Point[] A = {
      new Point(0.5,   2),
      new Point(-0.25, 1.25),
      new Point(-0.15, 0.35),
      new Point(-0.25, 0.75)
    };

    MySort.sort(A);

    for( int i = 0; i < A.length; i++ ) {
      System.out.println(A[i]);
    }

    String[] B = {
      "the", "man", "who", "died"
    };
    MySort.sort(B);

    for( int i = 0; i < B.length; i++ ) {
      System.out.println(B[i]);
    }
  }
}
Point.java
public class Point implements Comparable {
  private double x, y;

  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  public String toString() {
    return "(" + x + "," + y + ")";
  }

  public double mag() {
    return Math.sqrt(x * x + y * y);
  }

  public int compareTo(Object o) {
    Point p = (Point)o;

    return (int)Math.signum(this.mag() - p.mag());
  }
}

That's pretty cool! In the same program, we use MySort.sort() to sort Points and to sort Strings. Now we don't have to copy&paste every time we want to sort a new kind of object!

Note: this doesn't solve the sorting problem, however. What we can't do is use MySort.sort() to sort Strings alphabetically in one place, and then by length in another.

Java 8 - Interfaces change!

Java 8 Java changed the no-multiple-inheritance paradigm significantly. We note those changes here for extra information, but we encourage you not to use these new features unless you have a very well motivated reason to do so.

The first change is that interfaces can have data fields!!. You can actually have an interface with a variable which all the children inherit, but the big catch is that it has to be a static data field. This essentially enables global variables in interfaces, so every class that says they implement an interface can also have static variables accessible to it. Ok, fine. That's a nice convenience.

The second bigger change is that interfaces can have implemented methods!!. Ok this is a big one, breaking the paradigm because you can now inheriting multiple implementations. But again, there is a catch. You have to label these as default methods, meaning any class that implements the interface will indeed inherit the method, but if a class implements TWO interfaces that both have the same default method, the compiler fails. You can't multiple-inherit the same method because that's obviously ambiguous.

There you have it. Interfaces can have static variables, and they can have default methods. Here's an example with a field and a default method:

public interface Circley {
  public static final double PI = 3.14159;

  public abstract void drawme(); // normal abstract method

  public default double computeArea(float radius) { // new, inherited default method
    return PI * radius * radius;
  }
}

Interfaces: A cool trick — for-each loops

We'll teach you Iterators after the break. Come back here to see how interfaces fit in!

Another interface in the java API is Iterable. Our Queue is basically already an example of an Iterable, we merely need to state that explicitly (and add one little method to Queue.Iter) in order to "implement" the interface. The advantage to taking the time to make a class implement an interface, is that you then get to use all the code that assumes that interface — like making Point implement Comparable allowed us to sort Points with MySort.sort(). In this case, one advantage to implementing Iterable is that we can use Java's "for-each loops". Assume you have bar referring to an object of a type that implements the Iterable interface, where the objects you get from calls to "next()" have type Foo. Then you can iterate through all the objects in the collection bar with the following code:

for(Foo f : bar) {
   // do whatever with f
}
In the example below, we explicitly define Queue as implementing Iterable<String> (don't worry about the meaning of the angle-brackets right now) and then give a simple driver that does a for-each loop to iterate through the elements of the Queue.

Ex4.java
import java.util.*;

public class Ex4 {
  public static void main(String[] args) {
    Queue   Q  = new Queue();
    Scanner sc = new Scanner(System.in);
    String  s;

    while( !(s = sc.next()).equals("done") ) {
      Q.enqueue(s);
    }

    for( String t : Q )
      System.out.print(t.charAt(0));
    System.out.println();
  }
}
Queue.java
import java.util.*;

public class Queue implements Iterable<String>{
  public void enqueue(String s) {
    if( head == null ) {
      head = tail = new Node(s, null);
    } else {
      tail.next = new Node(s, null);
      tail      = tail.next;
    }
  }

  public String dequeue() {
    Node t = head;
    head = head.next;

    if( head == null )
      tail = null;
    return t.data;
  }

  public boolean empty() {
    return head == null;
  }

  public Iter iterator() {
    return new Iter(head);
  }

  public class Iter implements Iterator<String>{
    private Node curr;
    public Iter(Node start) {
      curr = start;
    }

    public boolean hasNext() {
      return curr != null;
    }

    public String next() {
      String s = curr.data;

      curr = curr.next;
      return s;
    }

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

  private class Node {
    public String data;
    public Node   next;
    public Node(String d, Node n) {
      data = d;
      next = n;
    }
  }

  private Node head = null, tail = null;
}