Comparable; generic binary search tree
Table of contents
Comparable interface
IntSearchTree relies on maintaining the binary search tree invariant to determine the location of a given element. However, the < (less than) and > (greater than) operators only work with numeric types such as int and double. In this lesson, we’ll see how we can generalize this idea to any data type with the help of the Comparable interface, and also write some of our own classes that implement the Comparable interface.
The Comparable interface is Java’s standard approach for defining a natural comparison order for any implementing class. The interface requires classes to implement a single method, compareTo.
public interface Comparable<T> {
public int compareTo(T o);
}
In contrast to other interfaces we’ve seen such as List, Set, Queue, and Map, the Comparable interface is used in a different way than the usual client-implementer relationship. The List interface, for example, is typically used by clients that want to store data in a list.
The Comparable interface, on the other hand, is designed to help data structure and algorithm implementers. For example, we can generalize IntSearchTree as a SearchTree class that only works with certain Comparable data types.
Implementer
public class SearchTree<T extends Comparable<T>> {
// pre : Binary search tree invariant.
// post: Returns true if the value is in this tree, false otherwise.
public boolean contains(T value) {
return contains(overallRoot, value);
}
private boolean contains(TreeNode root, T value) {
if (root == null) {
return false;
}
int cmp = value.compareTo(root.data);
if (cmp == 0) { // value == root.data
return true;
} else if (cmp < 0) { // value < root.data
return contains(root.left, value);
} else { // value > root.data
return contains(root.right, value);
}
}
...
}
T extends Comparable<T> is a bit of generic syntax that reads, “some data type T that can be compared against other instances of T”. This idea is more generally known as delegation. For example, instead of handling the logic for comparing strings inside of the SearchTree class, we ask the String class to define the relationship between any two strings.
Implementer
public class String implements Comparable<String> {
...
}
This way, the SearchTree class is generic: it doesn’t depend on any particular implementation details of String, and it supports any user-defined class as long as the class implements Comparable. Delegation of comparison allows the implementer of the String class to communicate information back to the SearchTree implementer.
Implementing Comparable
Suppose we have a Dog class, where each Dog has a name and a size.
Implementer
public class Dog {
private String name;
private int size;
...
}
Let’s say that we want to compare dogs by their size field so that smaller dogs come before larger dogs. There are a few steps to follow for Dog to implement Comparable.
- Declare the
Comparablerelationship to Java in the class header by implementingComparable<Dog>. - Implement the interface by adding a
public int compareTo(Dog other)method.
The compareTo method returns a negative integer if this < other, zero if this == other, or a positive integer if this > other. There’s a nice symmetry between the comparison operators and the compareTo method expectations.
- If
this < other, thenthis.compareTo(other) < 0. - If
this == other, thenthis.compareTo(other) == 0. - If
this > other, thenthis.compareTo(other) > 0.
Implementer
public int compareTo(Dog other) {
if (this.size < other.size) {
return -1;
} else if (this.size == other.size) {
return 0;
} else { // this.size > other.size
return 1;
}
}
The Java developers anticipated programmers would need to write classes that implemented Comparable, so they provided helper methods for many commonly-used data types including Integer, Double, and String. However, we can’t call this.size.compareTo(other.size) directly since size is an int primitive value, not an Integer object. To get around this unfortunate design decision, the Integer and Double classes provide a special static method compare that returns the compareTo result between two numbers, x and y.
Implementer
public int compareTo(Dog other) {
return Integer.compare(this.size, other.size);
}
Describe a small change that will order dogs largest to smallest.
Negate the result before returning it!
Implementer
public int compareTo(Dog other) {
return -Integer.compare(this.size, other.size);
}
Oftentimes, we’ll want to introduce more complex behavior for compareTo. If the two dogs are equal in size, then we want to compare on their name fields.
Implementer
public int compareTo(Dog other) {
int cmp = Integer.compare(this.size, other.size);
if (cmp == 0) {
cmp = this.name.compareTo(other.name);
}
return cmp;
}