The
public class WordList {
public static void main(String[] args) {
Set<String> s = new TreeSet<>();
Collections.addAll(s, args);
System.out.println(s);
}
}
public interface Comparable<T> {
int compareTo(T t);
}
compareTo
method is not declared in Object
. Rather, it is the sole method in the Comparable
interface.public class WordList {
public static void main(String[] args) {
Set<String> s = new TreeSet<>();
Collections.addAll(s, args);
System.out.println(s);
}
}
public interface Comparable<T> {
int compareTo(T t);
}
The general contract of the
compareTo
method is similar to that of equals
:
Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. Throws
ClassCastException
if the specified object’s type prevents it from being compared to this object.
In the following description, the notation
sgn
(expression) designates the mathematical signum function, which is defined to return -
1, 0, or 1, according to whether the value of expression is negative, zero, or positive.
• The implementor must ensure that
sgn(x.compareTo(y)) == -sgn(y. compareTo(x))
for all x
and y
. (This implies that x.compareTo(y)
must throw an exception if and only if y.compareTo(x)
throws an exception.)
• The implementor must also ensure that the relation is transitive:
(x. compareTo(y) > 0 && y.compareTo(z) > 0)
implies x.compareTo(z) > 0
.
• Finally, the implementor must ensure that
x.compareTo(y) == 0
implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z))
, for all z
.
• It is strongly recommended, but not required, that
(x.compareTo(y) == 0) == (x.equals(y))
. Generally speaking, any class that implements the Comparable
interface and violates this condition should clearly indicate this fact. The recommended language is “Note: This class has a natural ordering that is inconsistent with equals
.”
For example, consider the
BigDecimal
class, whose compareTo
method is inconsistent with equals
. If you create an empty HashSet
instance and then add new BigDecimal("1.0")
and new BigDecimal("1.00")
, the set will contain two elements because the two BigDecimal
instances added to the set are unequal when compared using the equals
method. If, however, You perform the same procedure using a TreeSet
instead of a HashSet
, the set will contain only one element because the two BigDecimal
instances are equal when compared using the compareTo
method. (See the BigDecimal
documentation for details.)
Writing a
compareTo
method is similar to writing an equals
method, but there are a few key differences.Because the Comparable
interface is parameterized, the compareTo
method is statically typed, so you don’t need to type check or cast its argument. If the argument is of the wrong type, the invocation won’t even compile. If the argument is null
, the invocation should throw a NullPointer-Exception
, and it will, as soon as the method attempts to access its members.
In a
compareTo
method, fields are compared for order rather than equality. To compare object reference fields, invoke the compareTo
method recursively. If a field does not implement Comparable
or you need a nonstandard ordering, use a Comparator
instead. You can write your own comparator or use an existing one, as in this compareTo
method for CaseInsensitiveString
in Item 10:
// Multiple-field Comparable with primitive fields
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(lineNum, pn.lineNum);
}
return result;
}
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(lineNum, pn.lineNum);
}
return result;
}
// Comparable with comparator construction methods
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);
}
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);
}
Occasionally you may see
compareTo
or compare
methods that rely on the fact that the difference between two values is negative if the first value is less than the second, zero if the two values are equal, and positive if the first value is greater. Here is an example:
// BROKEN difference-based comparator - violates transitivity!
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
};
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
};
Do not use this technique. It is fraught with danger from integer overflow and IEEE 754 floating point arithmetic artifacts
// Comparator based on static compare method
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return Integer.compare(o1.hashCode(), o2.hashCode());
}
};
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return Integer.compare(o1.hashCode(), o2.hashCode());
}
};
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 the comparator construction methods in the Comparator
interface.
No comments:
Post a Comment