Effective Java 的笔记,代码、英语原文为主,批注、翻译为辅。
Item 14: Consider implementing Comparable
考虑实现 Comparable 接口
Virtually all of the
value classes
in the Java platform libraries, as well as allenum types
,implement Comparable
. If you are writing a value class with an obvious natural ordering, such as alphabetical order, numerical order, or chronological order, you should implement the Comparable interface.
Multiple-field Comparable with primitive fields
If a class has multiple significant fields, the order in which you compare them is critical. Start with the most significant field and work your way down. If a comparison results in anything other than zero (which represents equality), you’re done; just return the result. If the most significant field is equal, compare the next-most-significant field, and so on, until you find an unequal field or compare the least significant field.
Here is a compareTo method for the PhoneNumber class demonstrating this technique:
|
|
Comparable with comparator construction methods
In Java 8, the Comparator interface was outfitted with a set of
comparator construction
methods, which enable fluent construction of comparators. These comparators can then be used to implement a compareTo method, as required by the Comparable interface. Many programmers prefer the conciseness of this approach, though it does come at a modest performance cost: sorting arrays of PhoneNumber instances is about 10% slower on my machine. When using this approach, consider using Java’sstatic import facility
so you can refer to static comparator construction methods by their simple names for clarity and brevity.
Here’s how the compareTo method for PhoneNumber looks using this approach:
|
|
This implementation builds a comparator at class initialization time
, using two comparator construction methods
.
Two comparator construction methods
comparingInt
The first is
comparingInt
. It is astatic
method that takes a key extractor function that maps an object reference to a key of type int and returns acomparator
that orders instances according to that key. In the previous example, comparingInt takes a lambda () that extracts the area code from a PhoneNumber and returns aComparator<PhoneNumber>
that orders phone numbers according to their area codes.
Note that the lambda
explicitly specifies the type of its input parameter (PhoneNumber pn)
. It turns out that in this situation, Java’s type inference isn’t powerful enough to figure the type out for itself, so we’re forced to help it in order to make the program compile.
thenComparingInt
If two phone numbers have the same area code, we need to further refine the comparison, and that’s exactly what the second comparator construction method,
thenComparingInt
, does.
It is an instance method on Comparator that takes an int key extractor function, and returns a comparator that first applies the original comparator and then uses the extracted key to break ties. You can stack up as many calls to thenComparingInt as you like, resulting in a lexicographic ordering. In the example above, we stack up two calls to thenComparingInt, resulting in an ordering whose secondary key is the prefix and whose tertiary key is the line number.
Note that we
did not have to specify the parameter type of the key extractor function
passed to either of the calls to thenComparingInt: Java’s type inference was smart enough to figure this one out for itself.
summary
The
Comparator
class has a full complement of construction methods. There are analogues to comparingInt and thenComparingInt for theprimitive types
long and double. The int versions can also be used for narrower integral types, such as short, as in our PhoneNumber example. The double versions can also be used for float. This provides coverage of all of Java’s numerical primitive types.
Comparator construction methods for object reference types
There are also comparator construction methods for object reference types.
The static method
, named comparing
, has two overloadings
.
- One takes a key extractor and uses the keys’ natural order.
- The second takes both a key extractor and a comparator to be used on the extracted keys.
There are three overloadings
of the instance method
, which is named thenComparing
.
- One overloading takes only a comparator and uses it to provide a secondary order.
- A second overloading takes only a key extractor and uses the key’s natural order as a secondary order.
- The final overloading takes both a key extractor and a comparator to be used on the extracted keys.
Summary
Use either a static compare method:
|
|
or a comparator construction method:
|
|
In summary, whenever you implement a value class that has a sensible ordering, you should have the class
implement the Comparable interface
so that its instances can be easily sorted, searched, and used in comparison-based collections.
When comparing field values in the implementations of the compareTo methods, avoid the use of the < and > operators. Instead, use the
static compare methods
in the boxed primitive classes or thecomparator construction methods
in theComparator interface
.