loading...

Effective Java Tuesday! Consider Implementing `Comparable`

kylec32 profile image Kyle Carter Updated on ・4 min read

Effective Java Review (36 Part Series)

1) Effective Java Tuesday! Let's Consider Static Factory Methods 2) Effective Java Tuesday! The Builder Pattern! 3 ... 34 3) Effective Java Tuesday! Singletons! 4) Effective Java Tuesday! Utility Classes! 5) Effective Java Tuesday! Prefer Dependency Injection! 6) Effective Java Tuesday! Avoid Creating Unnecessary Objects! 7) Effective Java Tuesday! Don't Leak Object References! 8) Effective Java Tuesday! Avoid Finalizers and Cleaners! 9) Effective Java Tuesday! Prefer try-with-resources 10) Effective Java Tuesday! Obey the `equals` contract 11) Effective Java Tuesday! Obey the `hashCode` contract 12) Effective Java Tuesday! Override `toString` 13) Effective Java Tuesday! Override `clone` judiciously 14) Effective Java Tuesday! Consider Implementing `Comparable` 15) Effective Java Tuesday! Minimize the Accessibility of Classes and Member 16) Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields 17) Effective Java Tuesday! Minimize Mutability 18) Effective Java Tuesday! Favor Composition Over Inheritance 19) Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It. 20) Effective Java Tuesday! Prefer Interfaces to Abstract Classes 21) Effective Java! Design Interfaces for Posterity 22) Effective Java! Use Interfaces Only to Define Types 23) Effective Java! Prefer Class Hierarchies to Tagged Classes 24) Effective Java! Favor Static Members Classes over Non-Static 25) Effective Java! Limit Source Files to a Single Top-Level Class 26) Effective Java! Don't Use Raw Types 27) Effective Java! Elminate Unchecked Warnings 28) Effective Java! Prefer Lists to Array 29) Effective Java! Favor Generic Types 30) Effective Java! Favor Generic Methods 31) Effective Java! Use Bounded Wildcards to Increase API Flexibility 32) Effective Java! Combine Generics and Varargs Judiciously 33) Effective Java! Consider Typesafe Hetergenous Containers 34) Effective Java! Use Enums Instead of int Constants 35) Effective Java! Use Instance Fields Instead of Ordinals 36) Effective Java! Use EnumSet Instead of Bit Fields

Today we come to the final chapter of this section about methods common to all objects. Unlike the rest of the methods talked about in this section this method is actually not a method on the class Object. That being said this method does affect the default operations in various other classes thus it is important to understand. The method we are talking about today is compareTo and it's related interface, Comparable.

So what is the purpose of the Comparable interface? It has a purpose in line with Object's equals however its purpose is to do order comparisons as well as equality comparisons. When you implement Comparable you are indicating that there is a natural order to your instances and offers a way to organize them in that way. Once you implement the interface, sorting an array of them is as simple as Arrays.sort(myArray).

Given that the natural order can be determined this makes it trivial to keep a sorted collection, search through values, or find maximum and minimum values. For example filling a TreeSet (which uses the compareTo method to sort its internal data structure) with String objects (which implement Comparable) you end up with an alphabetized list of values. The Comparable interface provides a lot of value, this is likely why practically all of Java's built in value types implement this interface. If the value class that you are writing has a natural ordering it can be a good idea to implement this interface as well.

Let's go over the interface and the contract. The Comparable interface looks something like the following:

public interface Comparable<T> {
  int compareTo(T t);
}

This is makes for a fairly straightforward interface. One method that takes in the generic type and returns an integer. So let's go over the contract for the compareTo method:

  • The compareTo method should return a negative number, zero, or positive number as the object is less than, equal to, or greater than the provided object.
  • Throw a ClassCastException if the provided object type is not compatible to be compared with the object.

The next few items take advantage of the signum mathematical function, denoted as sign() below. Simply put, this method returns a -1 for negative numbers, 0 for 0, and 1 for positive numbers.

  • For all x and y, sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  • Related to the above, x.compareTo(y) should only throw an exception if y.compareTo(x) also throws an exception.
  • Same as the equals function, compareTo should be transitive. Therefore, if x.compareTo(y) > 0 && y.compareTo(z) > 0 then x.compareTo(z) > 0. This should also work with < and ==

The comparison between compareTo and equals has been brought up before in this post and compareTo does return 0 when items are equal so should it be that when equals returns true should compareTo always return 0? The question comes down to, does object equality equate to natural ordering? Often it is the case that these are one in the same and can be surprising when they aren't the same.

Let's see an example of where this can be surprising. The BigDecimal class implements comparable in a way that is different than it's equals implementation. So given a HashSet (which takes advantage of the equals method) with two items new BigDecimal("1.0") and new BigDecimal("1.00"). The HashSet will end up with two entries in it. Compare this to putting those same two items into a TreeSet (which uses the compareTo method) and we would end up with only one item in the collection. Doesn't quite pass the principle of last surprise does it? Thus, even though it's not required, it is strongly suggested that when two objects return true to equals that those same two objects end up returning 0 from compareTo. At the very least, if this suggestion is not followed, it should be well documented that it doesn't follow this expectation.

So how does one go about writing a compareTo method. It's not too dissimilar from writing an equals method.

  1. Determine the order of significance of the fields of the class.
  2. Compare the fields by either recursively calling compareTo methods for reference types or use one of the built in BoxedTime.compare() methods such as Double.compare().
  3. Once you find a field that is unequal return the value for that field (or if there are no differences return 0).

Let's look at an example:

public int compareTo(PhoneNumber pn) {
  int result = Short.compare(areaCode, pn.areaCode);
  if (result == 0) {
    result = Short.compare(prefix, pn.prefix);
    if (result == 0) {
      result = Short.compare(lineNumber, pn.lineNumber);
    }
  }
  return result;
}

Not super complex however you can see how this could get pretty deep indentation. Java 8 provides an alternative that can end up much cleaner.

private static final Comparator<PhoneNumber> COMPARATOR = 
  comparingInt((PhoneNumber pn) -> pn.areaCode)
    .thenComparingInt(pn -> pn.prefix)
    .thenComparingInt(pn -> pn.lineNum);

public int compareTo(PhoneNumber pn) {
  return COMPARATOR.compare(this, pn);
}

As you can see this will be much simpler and cleaner. No matter how many items we are comparing it won't get any deeper. The trade-off being a little bit of performance.

Something that you will occasionally see that may look like a good idea at the time is taking advantage of the fact that the difference between two values is negative. Thus subtracting one from the other can lead to meeting the contract. The trouble with this is you can deal with integer overflow and other such problems. It's best to take the slight performance hit of using the comparing methods.

Unfortunately, with this method I don't know of a tool like Lombok that can generate the compareTo method for us. That being the case we do need to handle the creation of this method ourselves. Summing this chapter up, when you are creating a value type with a natural order you should consider implementing the Comparable interface. This allows your value type to be easily sorted, searched, and used in comparison-based collections.

Effective Java Review (36 Part Series)

1) Effective Java Tuesday! Let's Consider Static Factory Methods 2) Effective Java Tuesday! The Builder Pattern! 3 ... 34 3) Effective Java Tuesday! Singletons! 4) Effective Java Tuesday! Utility Classes! 5) Effective Java Tuesday! Prefer Dependency Injection! 6) Effective Java Tuesday! Avoid Creating Unnecessary Objects! 7) Effective Java Tuesday! Don't Leak Object References! 8) Effective Java Tuesday! Avoid Finalizers and Cleaners! 9) Effective Java Tuesday! Prefer try-with-resources 10) Effective Java Tuesday! Obey the `equals` contract 11) Effective Java Tuesday! Obey the `hashCode` contract 12) Effective Java Tuesday! Override `toString` 13) Effective Java Tuesday! Override `clone` judiciously 14) Effective Java Tuesday! Consider Implementing `Comparable` 15) Effective Java Tuesday! Minimize the Accessibility of Classes and Member 16) Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields 17) Effective Java Tuesday! Minimize Mutability 18) Effective Java Tuesday! Favor Composition Over Inheritance 19) Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It. 20) Effective Java Tuesday! Prefer Interfaces to Abstract Classes 21) Effective Java! Design Interfaces for Posterity 22) Effective Java! Use Interfaces Only to Define Types 23) Effective Java! Prefer Class Hierarchies to Tagged Classes 24) Effective Java! Favor Static Members Classes over Non-Static 25) Effective Java! Limit Source Files to a Single Top-Level Class 26) Effective Java! Don't Use Raw Types 27) Effective Java! Elminate Unchecked Warnings 28) Effective Java! Prefer Lists to Array 29) Effective Java! Favor Generic Types 30) Effective Java! Favor Generic Methods 31) Effective Java! Use Bounded Wildcards to Increase API Flexibility 32) Effective Java! Combine Generics and Varargs Judiciously 33) Effective Java! Consider Typesafe Hetergenous Containers 34) Effective Java! Use Enums Instead of int Constants 35) Effective Java! Use Instance Fields Instead of Ordinals 36) Effective Java! Use EnumSet Instead of Bit Fields

Posted on by:

kylec32 profile

Kyle Carter

@kylec32

Backend Architect at MasterControl

Discussion

markdown guide
 

love your weekly cycle, keep it up! :)

 

There's a small mistake in your example code. All comparisons should read:

if (result == 0) {