Effective.Java 读书笔记(12)关于Comparable接口

12. Consider implementing Comparable

大意为 考虑实现Comparable接口

对于Comparable接口来说,其主要方法应该是compareTo方法,可是这个方法并没有在Object里面声明,而是Comparable接口中唯一的方法,这个方法所能够产生的作用并不局限于简单的比较,还可以是有顺序的比较

换句话说,实现Comparable接口的类,都具有一种内在的排序关系(natural ordering),而且对该类的数组进行排序也是比较简单的,例如:

Arrays.sort(a);

对于储存在集合里面的实现了Comparable接口的对象进行搜索,计算极值之类的操作都十分简单,下面举个例子,实现了从命令行读取参数,并且自动排序并打印

public class ComparableTest {
    public static void main(String[] args){
        Set<String> s=new TreeSet<String>();
        Collections.addAll(s,args);
        System.out.println(s);
    }
}

我们在IntelliJ里面设定命令行参数,设定方法如下

主面板-> Run -> Edit Configurations

setThePara

如上图所示,我们把参数设成 “apple cat interest home bed”这5个字符串
得到的结果为

result

我们可以在参数后面继续加一个apple,你会发现结果还是一样的,这说明此内在排序关系还能够剔除重复的元素

可以看出来,这个接口的功能还是十分强大的,Java中的一些值依赖的类都实现了这个接口,所以当你想要实现排序,不管是字母顺序排序还是数值排序,都应该考虑实现这个接口

Comparable接口中的compareTo方法其实与我们之前所介绍的equals类似,按照两个对象的比较大小返回相应的结果,分别是0,正整数,负整数,如果无法比较的话就抛出一个ClassCastException

在这里我们引入一个数学函数signum也就是sgn符号函数,该函数根据自变量的符号分别返回-1,0,+1

接下来,利用这个数学函数,我们介绍一下实现Comparable接口的一些约定

  • 所有的x,y需满足,sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  • 传递性:x.compareTo(y) > 0 并且 y.compareTo(z) > 0,则有 x.compareTo(z) >0
  • 对于任意z,当x.compareTo(y)==0时,均有sgn(x.compareTo(z))==sgn(y.compareTo(z))
  • 强烈建议,x.compareTo(y)==0等价于x.equals(y) ,虽然这个不是必要的,但是如果违反这一条件,应该做出适当说明

以上是我们必需遵守的规范,如果违反了这些规范,那么就会破坏了一些依赖于比较关系的类,比如说一些有序的集合类,TreeSet,TreeMap,或者是一些工具类,比如Collection和Arrays,它们的内部都有搜索和排序算法

再此需要提及的是,实现了Comparble接口的类,如果你想要为它添加一些值依赖的域,那么首要的选项必然不是去直接添加成员域来进行扩展,比较好的方法应该是重新写一个类,在这个新的类里面有着扩展前的类的实例,并且包含一个“View”来返回这个实例

对着上面的约定中的最后一项,并不是真正的规则,而是同equals返回的结果一致,保持一种一致的顺序关系,当然了,一个类违反了这个小规则,也可以继续正常工作,不过当一个有序的集合的元素违反了这个规则,那么这个集合可能就不能遵守一些集合,比如Collection,Set,Map等的通用的接口约定,这是因为这些通用的接口约定是利用equals定义的

在这里举一个例子,BigDecimal的这个类,它的compareTo方法和equals方法并不一致,如果你用HashMap创建一个集合,并且添加new BigDecimal(1.0)和new BigDecimal(1.00)这两个实例,这个集合将包含两个元素,用TreeMap来实现的话就只有一个元素存在,这是因为前者是利用equals,后者是利用compareTo,我们实际来测试一下

public class ComparableTest {
    public static void main(String[] args){
        TreeSet<BigDecimal> set1= new TreeSet<>();
        HashSet<BigDecimal> set2=new HashSet<>();
        set1.add(new BigDecimal(1.0));
        set1.add(new BigDecimal(1.00));
        set2.add(new BigDecimal(1.0));
        set2.add(new BigDecimal(1.00));
        System.out.println("Set1's size is "+set1.size());
        System.out.println("Set2's size is "+set2.size());
        System.out.println(new BigDecimal(1.00).compareTo(new BigDecimal(1.0)));
        System.out.println(new BigDecimal(1.00).equals(new BigDecimal(1.0)));
    }
}

结果如下图所示:
result

可以看出,书中所提的bug在1.8已经被修复了,但是BigDecimal的两个方法不一致的问题还是存在

编写compareTo方法和equals方法差不多,但是有几个重要的区别,Comparable接口是参数化的,并且comparable方法是静态的类型,那么我们就不用对参数进行类型转换,如果参数类型不合适的话,甚至无法通过编译

注意到compareTo方法其实是顺序的比较,比较对象的引用域的时候我们可以递归地使用compareTo方法来解决,如果一个域没有实现Comparable接口,或者我们需要一种另类的排序关系来比较的话,可以考虑使用Comparator,使用已有的或者是自己编写一个

需要提及的是,当一个类有多个关键域的时候,我们需要自己拟定比较的顺序,举一个例子,在笔记9中出现的PhoneNumber类

public int compareTo(PhoneNumber pn) {
    // Compare area codes
    if (areaCode < pn.areaCode)
        return -1;
    if (areaCode > pn.areaCode)
        return 1;
    // Area codes are equal, compare prefixes
    if (prefix < pn.prefix)
        return -1;
    if (prefix > pn.prefix)
        return 1;
    // Area codes and prefixes are equal, compare line numbers
    if (lineNumber < pn.lineNumber)
        return -1;
    if (lineNumber > pn.lineNumber)
        return 1;
    return 0; // All fields are equal
}

由于我们并不要求返回的值,只要求返回的符号,所以以上的代码可以简化成

public int compareTo(PhoneNumber pn) {
    // Compare area codes
    int areaCodeDiff = areaCode - pn.areaCode;
    if (areaCodeDiff != 0)
        return areaCodeDiff;
    // Area codes are equal, compare prefixes
    int prefixDiff = prefix - pn.prefix;
    if (prefixDiff != 0)
        return prefixDiff;
    // Area codes and prefixes are equal, compare line numbers
    return lineNumber - pn.lineNumber;
}

当然这样做要注意两者的差值不能超过int的表示范围,否则就溢出了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值