Effective Java Item12-考虑实现Comparable接口

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;

public class OrderedArray implements java.lang.Comparable<OrderedArray> {

       private int displayOrder;

       public OrderedArray(int displayOrder) {

              this.displayOrder = displayOrder;

       }

       public int compareTo(OrderedArray o) {

              int result = this.getDisplayOrder() - o.getDisplayOrder();

              if (result == 0) {

                     return 0;

              }

              return result > 0 ? 1 : -1;

       }

       public int getDisplayOrder() {

              return displayOrder;

       }

       /**

        * @param args

        */

       public static void 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;

public class AphabetArguments {

       public static void 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接口,那么ListsArrays包含的实例可以通过Collections.sort方法和Arrays.sort方法进行自动排序。实现该接口的对象可以作为排序mapkey,也可以作为排序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.

 */

public class Inconsistent implements Comparable<Inconsistent> {

       private long milliseconds = 0;

       public Inconsistent(long milli) {

              this.milliseconds = milli;

       }

       public int compareTo(Inconsistent o) {

              long result = this.getMilliseconds() - o.getMilliseconds();

              if (result == 0) {

                     return 0;

              }

              return result > 0 ? 1 : -1;

       }

       @Override

       public boolean equals(Object obj) {

              if (this == obj) {

                     return true;

              }

              if (obj instanceof Inconsistent) {

                     Inconsistent iect = (Inconsistent) obj;

                     long milli = iect.getMilliseconds();

                     return this.milliseconds == milli - 1;

                     // return this.milliseconds == milli;

              }

              return false;

       };

       @Override

       public String toString() {

              return super.toString() + ", " + milliseconds;

       }

       public long getMilliseconds() {

              return milliseconds;

       }

       /**

        * @param args

        */

       public static void 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),在putget的时候不再使用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.0BigDecimal实例2.00来说,如果将它们添加到HashSet中,那么全部添加成功,因为它们的equals方法返回false。但是如果要添加到TreeSet中,那么将只能成功添加一个,因为它们的compareTo方法返回0

对于整型数,使用><来进行比较,对于doublefloat,使用Double.compareFloat.compare方法,对于数组,针对每个元素使用上述办法将进行比较。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值