3.3 Examples of Interfaces
At first glance, interfaces don't seem to do very much. An interface is just a set of methods that a class promises to implement. To make the importance of interfaces more tangible, the following sections show you four examples of commonly used interfaces from the Java API.
3.3.1 The Comparable Interface
Suppose you want to sort an array of objects. A sorting algorithm repeatedly compares elements and rearranges them if they are out of order. Of course, the rules for doing the comparison are different for each class, and the sorting algorithm should just call a method supplied by the class. As long as all classes can agree on what that method is called, the sorting algorithm can do its job. That is where interfaces come in.
If a class wants to enable sorting for its objects, it should implement the Comparable interface. There is a technical point about this interface. We want to compare strings against strings, employees against employees, and so on. For that reason, the Comparable interface has a type parameter.
public interface Comparable<T> {
int compareTo(T other);
}
For example, the String class implements Comparable<String> so that its compareTo method has the signature
int compareTo(String other)
When calling x.compareTo(y), the compareTo method returns an integer value to indicate whether x or y should come first. A positive return value (not necessarily 1) indicates that x should come after y. A negative integer (not necessarily -1) is returned when x should come before y. If x and y are considered equal, the returned value is 0.
Note that the return value can be any integer. That flexibility is useful because it allows you to return a difference of integers. That is handy, provided the difference cannot produce integer overflow.
public class Employee implements Comparable<Employee> {
...
public int compareTo(Employee other) {
return getId() - other.getId(); // Ok if IDs always ≥ 0
}
}
When comparing floating-point values, you cannot just return the difference. Instead, use the static Double.compare method. It does the right thing, even for ±∞ and NaN.
Here is how the Employee class can implement the Comparable interface, ordering employees by salary:
public class Employee implements Comparable<Employee> {
...
public int compareTo(Employee other) {
return Double.compare(salary, other.salary);
}
}
The String class, as well as over a hundred other classes in the Java library, implements the Comparable interface. You can use the Arrays.sort method to sort an array of Comparable objects:
String[] friends = { "Peter", "Paul", "Mary" };
Arrays.sort(friends); // friends is now ["Mary", "Paul", "Peter"]
3.3.2 The Comparator Interface
Now suppose we want to sort strings by increasing length, not in dictionary order. We can't have the String class implement the compareTo method in two ways—and at any rate, the String class isn't ours to modify.
To deal with this situation, there is a second version of the Arrays.sort method whose parameters are an array and a comparator—an instance of a class that implements the Comparator interface.
public interface Comparator<T> {
int compare(T first, T second);
}
To compare strings by length, define a class that implements Comparator<String>:
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
To actually do the comparison, you need to make an instance:
Comparator<String> comp = new LengthComparator();
if (comp.compare(words[i], words[j]) > 0) ...
Contrast this call with words[i].compareTo(words[j]). The compare method is called on the comparator object, not the string itself.
To sort an array, pass a LengthComparator object to the Arrays.sort method:
String[] friends = { "Peter", "Paul", "Mary" };
Arrays.sort(friends, new LengthComparator());
Now the array is either ["Paul", "Mary", "Peter"] or ["Mary", "Paul", "Peter"].
You will see in Section 3.4.2, “Functional Interfaces” (page 115) how to use a Comparator much more easily, using a lambda expression.
3.3.3 The Runnable Interface
At a time when just about every processor has multiple cores, you want to keep those cores busy. You may want to run certain tasks in a separate thread, or give them to a thread pool for execution. To define the task, you implement the Runnable interface. This interface has just one method.
class HelloTask implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("Hello, World!");
}
}
}
If you want to execute this task in a new thread, create the thread from the Runnable and start it.
Runnable task = new HelloTask();
Thread thread = new Thread(task);
thread.start();
Now the run method executes in a separate thread, and the current thread can proceed with other work.
3.3.4 User Interface Callbacks
In a graphical user interface, you have to specify actions to be carried out when the user clicks a button, selects a menu option, drags a slider, and so on. These actions are often called callbacks because some code gets called back when a user action occurs.
In Java-based GUI libraries, interfaces are used for callbacks. For example, in JavaFX, the following interface is used for reporting events:
public interface EventHandler<T> {
void handle(T event);
}
This too is a generic interface where T is the type of event that is being reported, such as an ActionEvent for a button click.
To specify the action, implement the interface:
class CancelAction implements EventHandler<ActionEvent> {
public void handle(ActionEvent event) {
System.out.println("Oh noes!");
}
}
Then, make an object of that class and add it to the button:
Button cancelButton = new Button("Cancel");
cancelButton.setOnAction(new CancelAction());
Of course, this way of defining a button action is rather tedious. In other languages, you just give the button a function to execute, without going through the detour of making a class and instantiating it. The next section shows how you can do the same in Java.