Reading

Required: These notes!
Recommended: Java in a Nutshell, Section 1 or Chapter 6.

Overview

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

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

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 SI110 you may remember the importants of such logs for webserver 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.

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 abstrct (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!

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

Implementing and interface vs. Extending a class

It's natural to be confused about the two very similar ideas of "exending 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 mechansim 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 int he 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:

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.

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.

Interfaces: A cool trick — for-each loops

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