Effective Java 2nd Edition Reading Notes
Item12: Consider Implementing Comparable interface
考虑实现Comparable接口
compareTo方法并不是java.lang.Object类定义的方法。它是Comparable接口中的唯一的一个方法。它和Object类的equals方法类似,只是它允许指定自定义的比较,而不是简单的相等性比较;并且它是通用的。类通过实现Comparable接口来声明它的实例具有顺序的。
如果类实现了Comparable接口,那么如果一个数组的元素是该类的实例,那么可以通过使用Arrays.sort()进行排序。
package com.googlecode.javatips4u.effectivejava.comparable;
import java.util.Arrays;
publicclass OrderedArray implements java.lang.Comparable<OrderedArray> {
privateintdisplayOrder;
public OrderedArray(int displayOrder) {
this.displayOrder = displayOrder;
}
publicint compareTo(OrderedArray o) {
int result = this.getDisplayOrder() – o.getDisplayOrder();
if (result == 0) {
return 0;
}
return result > 0 ? 1 : -1;
}
publicint getDisplayOrder() {
returndisplayOrder;
}
/**
* @param args
*/
publicstaticvoid main(String[] args) {
OrderedArray small = new OrderedArray(1);
OrderedArray big = new OrderedArray(2);
OrderedArray[] array = { big, small };
System.out.println(array[0].displayOrder); // 2
Arrays.sort(array);
System.out.println(array[0].displayOrder); // 1
}
}
java.lang.String类实现了Comparable接口。如果一个结合类的元素为String类型,那么就可以很容易的实现检索,极限值计算以及维护集合的顺序。
package com.googlecode.javatips4u.effectivejava.comparable;
import java.util.Set;
import java.util.TreeSet;
publicclass AphabetArguments {
publicstaticvoid main(String[] args) {
/**
* This class guarantees that the sorted set will be in ascending
* element order, sorted according to the natural order of the elements
* (see Comparable), or by the comparator provided at set creation time,
* depending on which constructor is used.
*/
Set<String> alpha = new TreeSet<String>();//
alpha.add(“c”);
alpha.add(“z”);
alpha.add(“h”);
System.out.println(alpha);//[c, h, z]
}
}
通过实现Comparable接口,类可以和泛型以及所有的集合类进行互操作。
差不多Java提供的值类型的类都实现了Comparable接口。如果要设计自定义的值类型的类的话,并且类的实例是有一个自然的顺序的(字母表顺序,数字编号顺序或者时间顺序等等),那么应该考虑实现Comparable接口。
java.lang.Comparable<T>接口规定了实现该接口的类的实例的顺序。该顺序和类的自然顺序相关,该接口定义的compareTo方法是该类的自然比较方法。如果类实现了Comparable接口,那么Lists和Arrays包含的实例可以通过Collections.sort方法和Arrays.sort方法进行自动排序。实现该接口的对象可以作为排序map的key,也可以作为排序set的元素,此时,不需要调用sort方法就可以自动的排序。
类的自然排序和equals方法是一致的,当且仅当e1.compareTo((Object)e2)==0 <==> e1.equals((Object)e2)==true。注:null不是任何类的实例,而e.compareTo(null)应该抛出NullPointerException,而e.equals(null)返回false。[因为compareTo方法是返回整型的,无法指定一个整型数来提示输入参数是null]。
强烈建议自然顺序和equals保持一致性。否则的话,以该类的实例为元素或者key的有序set和有序map在没有显式使用比较器的时候会behave “strangely”。
例:
package com.googlecode.javatips4u.effectivejava.comparable;
import java.util.Set;
import java.util.TreeSet;
/**
* Inconsistent equals and compareTo.
*/
publicclass Inconsistent implements Comparable<Inconsistent> {
privatelongmilliseconds = 0;
public Inconsistent(long milli) {
this.milliseconds = milli;
}
publicint compareTo(Inconsistent o) {
long result = this.getMilliseconds() – o.getMilliseconds();
if (result == 0) {
return 0;
}
return result > 0 ? 1 : -1;
}
@Override
publicboolean equals(Object obj) {
if (this == obj) {
returntrue;
}
if (obj instanceof Inconsistent) {
Inconsistent iect = (Inconsistent) obj;
long milli = iect.getMilliseconds();
returnthis.milliseconds == milli – 1;
// return this.milliseconds == milli;
}
returnfalse;
};
@Override
public String toString() {
returnsuper.toString() + “, ” + milliseconds;
}
publiclong getMilliseconds() {
returnmilliseconds;
}
/**
* @param args
*/
publicstaticvoid main(String[] args) {
Inconsistent iect1 = new Inconsistent(100);
Inconsistent iect2 = new Inconsistent(100);
Set<Inconsistent> set = new TreeSet<Inconsistent>();
System.out.println(set.add(iect1));// true
System.out.println(set.add(iect2));// false
System.out.println(iect1.equals(iect2));// false
System.out.println(set.contains(iect1));// true
System.out.println(set.contains(iect2));// true
}
}
在上面的例子中,虽然set中指添加了一个元素,但是contains方法中包含了两个元素。因为对于有序的Set来说(Sun JVM),在put和get的时候不再使用equals方法进行判断,而是使用compareTo方法来判断。(请参考下面的TreeMap的实现)。
/**
TreeMap#
private Entry<K,V> getEntry(Object key) {
……
while (p != null) {
int cmp = compare(k, p.key);
if (cmp == 0)
return p;
……
}
public V put(K key, V value) {
……
while (true) {
int cmp = compare(key, t.key);
if (cmp == 0) {
return t.setValue(value);
}
……
}
}
*/
在Java的核心类中,java.math.BigDecimal类也没有实现这个一致性。
equals
/**
* Compares this <tt>BigDecimal</tt> with the specified
* <tt>Object</tt> for equality. Unlike {@link
* #compareTo(BigDecimal) compareTo}, this method considers two
* <tt>BigDecimal</tt> objects equal only if they are equal in
* value and scale (thus 2.0 is not equal to 2.00 when compared by
* this method).
*/
compareTo
/**
* Compares this <tt>BigDecimal</tt> with the specified
* <tt>BigDecimal</tt>. Two <tt>BigDecimal</tt> objects that are
* equal in value but have a different scale (like 2.0 and 2.00)
* are considered equal by this method. This method is provided
* in preference to individual methods for each of the six boolean
* comparison operators (<, ==, >, >=, !=, <=). The
* suggested idiom for performing these comparisons is:
* <tt>(x.compareTo(y)</tt> <<i>op</i>> <tt>0)</tt>, where
* <<i>op</i>> is one of the six comparison operators.
*/
Comparable#compareTo
/**
* 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.<p>
*/
In the following description, the notation sgn(expression) designates the math-ematical 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 sgn(x.compareTo(y)) == -sgn(y.compare-To(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.com-pareTo(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 strictly 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.”
违反compareTo约定的类将会影响那些依赖comparison的类,例如Collections.sort, Arrays.sort, TreeSet, TreeMap等。
以上协议和equals的自反性,对称性,传递性是一样的。
(特别注意的是第二种[CaseInsensitiveString&String]和第三种情况[Point&ColorPoint,使用组合而非继承实体类])
对于最后一个建议,例如对于BigDecimal来说,BigDecimal实例2.0和BigDecimal实例2.00来说,如果将它们添加到HashSet中,那么全部添加成功,因为它们的equals方法返回false。但是如果要添加到TreeSet中,那么将只能成功添加一个,因为它们的compareTo方法返回0。
对于整型数,使用>和<来进行比较,对于double和float,使用Double.compare和Float.compare方法,对于数组,针对每个元素使用上述办法将进行比较。
如果实例有多个值成员变量,那么要先比较重要的,依次进行。
上帝助自助者。