Effective Java 对于所有对象都通用的方法 12.考虑实现Comparable(可以比较)接口

compareTo方法并没有在Object中声明。相反,它是Comparable接口中唯一的一个方法。compareTo方法不但允许进行简单的同等性比较,而且允许执行顺序比较,除此之外,它与Object的equals方法具有相似的特性,它还是个泛型。类实现了Comparable接口,就表明它的实例具有内在的排序关系(natural ordering)。为实现Comparable接口的对象数组进行排序,就这么简单:

Arrays.sort(a);

a:实现Comparable接口的对象数组

对于存储在集合中的Comparable对象进行搜索、计算极限值以及自动维护也同样简单。

例 :
下面的程序依赖于String实现了Comparable接口,它去掉了命令行参数列表中的重复参数,并按字母表顺序打印出来了:

public class WordList {
    public static void main(String[] args) {
        Set<String> s = new TreeSet<String>();
        Collection.addAll(s, args);//添加数据 排序
        System.out.println(s);
    }
}

一旦类实现了Comparable接口,它就可以跟许多泛型算法(generic algorithm)以及依赖于该接口的集合实现(collection implementation)进行协作。付出了很小的努力就可以获得非常强大的功能。事实上,Java平台类库中的所有值类(value classes)都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,如:字母顺序、数值顺序、年代顺序…就应该考虑实现这个接口:

public interface Comparable<T> {
int compareTo(T t);
}

compareTo方法的通用约定和equals方法的相似:
将一个对象与指定对象进行比较。当该对象小于、等于或者大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法和该对象进行比较,则抛出ClassCastException异常。

在下面的说明中,符号sgn(表达式)表示数学中的signum函数,它根据表达式(expression)的值为负值、零和正值,分别返回-1、0、1。

1.实现者必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。(这也暗示着,当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才抛出异常。)

2.实现者还必须确保这个比较关系是可传递的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示着x.compareTo(z) > 0也成立。

3.最后,实现者必须确保x.compareTo(y) == 0暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))。

4.强烈建议(x.compareTo(y) == 0) == (x.equals(y)),但是这并非绝对必要。一般来说,任何实现了Comparable接口的类,若违反了这个条件,都应该明确予以说明。

在类的内部,任何合理的顺序关系都可以满足compareTo的约定。在跨越不同类的时候,compareTo可以不做比较:如果两个被比较的对象引用不同类的对象,compareTo可以抛出ClassCastException异常。通常,这正是compareTo在这种情况下应该做的事情,如果类设置了确定的参数,这也正式它要做的事情。虽然以上约定没有把跨类之间的比较排除在外,但是从Java1.6发行版本开始,Java平台类库中就没有支持跨类比较的这种特性了。

就好像违反了hashCode约定的类会破坏其他依赖于散列算法的做法的情况一样,违反了compareTo约定的类也会破坏其他依赖于比较关系的类。依赖于比较关系的类包括有序集合类TreeSet和TreeMap、以及工具类Collections和Arrays,它们内部包含有搜索和算法排序。

例如,考虑BigDecimal类,它的compareTo方法和equals方法不一致。如果你创建了一个HashSet实例,并且添加了new BigDecimal(“1.0”)和new BigDecimal(“1.00”),这个集合就将包含两个元素,因为新增到集合中的两个BigDecimal实例,通过equals方法来比较的时候是不相等的。然而如果你使用TreeSet而不是HashSet来执行同样的过程,集合中将只包含一个元素,因为这两个BigDecimal实例在通过compareTo方法进行比较的时候是相等的。(详情请参阅BigDecimal文档)

编写compareTo方法与编写equals方法非常相似,但也存在几处重大的差别。因为Compareable接口是参数化的,而且comparable方法是静态的类型,因此不必进行类型检查,也不必对它的参数进行类型的转换。如果参数的类型不合适,这个调用甚至无法编译。如果参数为null,这个调用应该抛出NullPointerException异常,并且一旦该方法试图访问它的成员变量时就应该抛出。

CompareTo方法中的域的比较是顺序的比较,而不是同等性的比较。比较对象引用域可以是通过递归地调用compareTo方法来实现。如果一个域并没有实现Compareable接口,或者你需要使用一个非标准的排序关系,就可以使用一个显示的Comparator来代替。或者是编写自己的Comparator,或者使用已有的Comparator,比如针对第8条中CaseInsensitiveString类的这个compareTo方法使用一个已有 的Comparator:

public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> {
    public int comparaTo(CaseInsensitiveString cis) {
        return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
    }

    ... //Remainder omitted
}

比较整数型基本类型的域,可以使用关系操作符<和>。例如,浮点域用Double.compare或者Float.compare,而不用关系操作符,当应用到浮点值时,它们没有遵守compareTo的通用约定。对于数组域,则需要把这些指导原则应用到每个元素上。

如果一个类有多个关键域,那么,按什么样的顺序来比较这些域是非常关键。你必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果(0代表着相等),则整个比较操作结束,并返回该结果。如果最关键的域是相等的,则进一步比较次最关键的域,以此类推,如果所有域都是相等的,则对象就是相等的,并才返回0。下面通过第9条中的PhoneNumber类的compareTo方法来说明这种方法:

 public int compareTo(PhoneNumber pn) {
        if (areaCode < pn.areaCode) 
            return -1if(areaCode > pn.areaCode)
            return 1;

        if (prefix < pn.prefix)
            return -1;
        if (prefix > pn.prefix)
            return 1;

        if (lineNumber < pn.lineNumber)
            return -1;
        if (lineNumber > pn.lineNumber)
            return 1;

        return 0;
    }
}

虽然这个方法可行,但它还可以进行改进。回想一下compareTo方法的约定,并没有指定返回值的大小(magnitude),而只是指定了返回值的符号。可以利用这一点来简化代码,或许还可以提高它的运行速度:

public int compareTo(PhoneNumber pn) {
    int areaCodeDiff = areaCode - pn.areaCode;
    if (areaCodeDiff != 0)
        return areaCodeDiff;

    int prefixDiff = prefix - pn.prefix;
    if (0 != prefixDiff)
        return prefixDiff;

    return lineNumber - pn.lineNumber;
}

这项技巧在这里能够工作的很好,但是用起来要非常小心,除非你确定相关的域不会为负值,或者更一般的情况:最小和最大的可能域值之差不予或等于INTEGER.MAX_VALUE,否则就不要使用这种方法。

猜你喜欢

转载自blog.csdn.net/weixin_39923324/article/details/78027042
今日推荐