Page 2 of 7
public String toString() {
return "A Foo Object." }
The method shown above is a perfectly legitimate implementation of toString according to the Java language specification, and yet it has the annoying side effect of causing objects of this type to
be non-sortable. Every instance of this object type will return the same string representation and thus will not be distinguishable
in the BinarySearchTree code.>
The issues involved in using toString can be evaluated in The Java Language Specification by James Gosling, Bill Joy, and Guy Steele. Taken from the specification, the contract for toString is as follows:
"The general contract of toString is that it returns a string that 'textually represents' this object. The idea is to provide a concise but informative representation
that will be useful to the person reading it."
As you can see, the specification doesn't say anything about being unique across object instances.
To get the guarantee that the programmer will know what we wanted for our keys, we can define an interface to provide a contract
where Object did not write into the contract that toString could be used as a unique key. This way, we can make the contract for that interface clear to its implementers. The canonical
example is to define an interface, ours is named SearchKey, that defines the contract for a key used for searching. The code
for SearchKey is shown below.
public interface SearchKey {
/**
* returns < 0 if search key o is less than this object.
* 0 if search key o is equal to this object.
* > 0 if search key o is greater than this object.
*/
abstract int compareKey(SearchKey o);
/** Return true if passed Search Key matches this one */
abstract boolean equalKey(SearchKey o);
}
This interface defines the method compareKey to have semantics that are equivalent to the compareTo method in class String. I avoid reusing the same, or common, method names in the interface definition as Java is unable to distinguish between two
methods with the same signature in two separate interfaces. It is poor form to choose method names that might easily collide
with other interface definitions. The second method returns a boolean value indicating that two keys are equivalent.
The contract, then, is that any object implementing this interface can be used as an index key to our container. The most
common implementation of equalKey might be similar to the code shown below.
public boolean equalKey(SearchKey o) {
return (compareKey(o) == 0);
}
The interface explicitly calls out the requirements of its implementation. Therefore, programmers implementing this interface
in their objects will understand the underlying contract between the container and its keys. By combining the two methods
equalKey and compareKey, the invariants of the binary tree search algorithms can be maintained.
Using this interface in our container is a bit more difficult. We cannot change the method signatures of get, put, and remove, as they are constrained by the Dictionary superclass. However, we can force an error if the client fails to abide by our rules. As in the first approach, we define
a private helper method to convert from the Object class reference to a SearchKey reference. This method is called keyForObject and it is shown in the code below.