Effective Java Item12

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 (&lt;, ==, &gt;, &gt;=, !=, &lt;=). The

* suggested idiom for performing these comparisons is:

* <tt>(x.compareTo(y)</tt> &lt;<i>op</i>&gt; <tt>0)</tt>, where

* &lt;<i>op</i>&gt; 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方法,对于数组,针对每个元素使用上述办法将进行比较。

如果实例有多个值成员变量,那么要先比较重要的,依次进行。

上帝助自助者。

Effective Java Item12

相关文章:

你感兴趣的文章:

标签云: