Comparable和Comparator解析

Comparable<T>接口

Comparable<T>是一个接口,T是当前对象需要去比较的对象的类型;

实现这个接口的类会被赋予一个顺序,称为自然顺序,类的compareTo方法称为自然比较方法;

正如上一篇笔记Arrays.sort() Collections.sort() 自定义排序中所说,实现了Comparable接口的类,使用列表或数组时可以无需指定比较器Comparator即可使用Collections.sort()或Arrays.sort()进行排序;

实现这个接口的类的对象还可以当做sorted map的键或sorted set中的值;

其中唯一的方法int compareTo(T o),将当前对象和指定对象进行比较,返回负整数、0、正整数,分别表示当前对象小于、等于、大于指定对象;

对于类C的每个e1和e2,当且仅当e1.compareTo(e2)==0具有与e1.equals(e2)相同的布尔值时,类C的自然顺序才被称为与equals一致。另外,由于null不是任何类的实例,e.compareTo(null)应该抛出NullPointerException,即使e.equals(null)返回false。

API中强烈建议自然顺序与equals方法保持一致,如果不保持一致,容易造成某些集合的使用出现反常,如sorted map和sorted set,因为会违反由equals方法定义的一般约定;

当自然顺序,也就是compareTo方法和equals方法不保持一致时,就会出现如下情况:equals方法判断不相等的对象,但无法添加进集合,因为集合添加元素时基于自然顺序,也就是compareTo比较,此时两者不相等,这就违反了由equals方法定义时的一般约束;

// 实体类
class CompEntity implements Comparable<CompEntity>{
    public int first;
    public int second;

    public CompEntity(int first, int second) {
        this.first = first;
        this.second = second;
    }
    @Override
    public int compareTo(CompEntity o) {
        return this.first - o.first; // 此时compareTo方法和equals方法不一致
        /*if(this.first - o.first == 0 && this.second - o.second == 0){  // 此时compareTo方法和equals方法一致
            return 0;
        }
        return 1;*/
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CompEntity that = (CompEntity) o;
        return first == that.first && second == that.second;
    }
    @Override
    public String toString() {
        return "CompEntity{" +
                "first=" + first +
                ", second=" + second +
                '}';
    }
}

public class CompComparable {
    public static void main(String[] args) {
        CompEntity s1 = new CompEntity(1, 1);
        CompEntity s2 = new CompEntity(1, 3);
        System.out.println("s1:" + s1);
        System.out.println("s2:" + s2);
        SortedSet<CompEntity> sortedSet = new TreeSet<>();
        sortedSet.add(s1);
        sortedSet.add(s2);
        // equals在CompEntity类中重写了,当类中两个变量都相等时返回true
        System.out.println("s1.equals(s2):" + s1.equals(s2));
        System.out.println("s1.compareTo(s2):" + s1.compareTo(s2));
        System.out.println("sortedSet()中元素个数:" + sortedSet.size());
        /**
         * 这里的结果分别是false、0、1
         * 首先s1.equals(s2)返回false,很明显,因为重写了equals方法,所以需要两个变量都相同才返回true,很明显这里不是
         * s1.compareTo(s2)返回0,说明基于当前的实现方式,两者是相等的,和equals的结果相反
         * sortedSet()中元素个数为1,即sortedSet中只添加进去了一个元素,即先添加进去的s1
         *      而sortedSet()基于compareTo方法判断出s1和s2相等,所以无法放进去,这就违反了TreeSet基于equals的一般约束
         *      也就是说,按道理,s2也应该是能放进去sortedSet()中的,因为基于equals方法,s1和s2是不等的,当然,这一点不强制
         *      但在Java已有类中,除BigDecimal外,其他实现Comparable接口的类均实现了compareTo和equals的一致
         */

        /**
         * BigDecimal的equals返回true必须数值和表现形式都一样
         * BigDecimal的自然比较顺序允许数值相同但表现形式不同
         * 所以以下结果分别返回false、0
         */
        BigDecimal b1 = new BigDecimal("6.0");
        BigDecimal b2 = new BigDecimal("6.00");
        System.out.println("b1:" + b1);
        System.out.println("b2:" + b2);
        System.out.println("b1.equals(b2):" + b1.equals(b2));
        System.out.println("b1.compareTo(b2):" + b1.compareTo(b2));
    }
}
s1:CompEntity{first=1, second=1}
s2:CompEntity{first=1, second=3}
s1.equals(s2):false
s1.compareTo(s2):0
sortedSet()中元素个数:1
b1:6.0
b2:6.00
b1.equals(b2):false
b1.compareTo(b2):0

Comparator<T>接口

T指该比较器需要进行比较的对象类型;这是一个功能性接口,所以可以使用lambda或方法引用赋值;

Comparator具有比较功能,可以对对象集合施加一个顺序,可以用作如下场景:

  1. Collections.sort和Arrays.sort精确控制排序顺序;
  2. 控制已有的如sorted sets或sorted maps等数据结构的内部元素顺序;
  3. 给没有自然顺序的集合提供排序方式;

同样的,和equals一致原则,是两者返回的结果需要保持一致;

同上,使用sorted set (or sorted map)时,需要注意比较器和equals方法的一致性问题,否则会出现反常行为;
也就是说当使用Comparator比较器替代自然排序的时候,也有类似于Comparable一样的问题:另外,通常将比较器实现序列化,这样可以保证如使用了比较器的sorted set (or sorted map)时能够序列化;

public class CompComparator {
    public static void main(String[] args) {
        CompEntity s1 = new CompEntity(1, 1);
        CompEntity s2 = new CompEntity(1, 3);
        System.out.println("s1:" + s1);
        System.out.println("s2:" + s2);
        /**
         * 自定义比较器,同equals方法一致,也就是两者得到的结果是一致的
         */
        Comparator<CompEntity> com = new Comparator<CompEntity>() {
            @Override
            public int compare(CompEntity o1, CompEntity o2) {
                if(o1.first - o2.first == 0 && o1.second - o2.second == 0){  // 此时compareTo方法和equals方法一致
                    return 0;
                }
                return 1;
            }
        };
        // 将比较器通过TreeSet构造器传入,此时通过比较器实现的TreeSet比较方式和equals是一致的
        SortedSet<CompEntity> sortedSet = new TreeSet<>(com);
        sortedSet.add(s1);
        sortedSet.add(s2);
        // equals在CompEntity类中重写了,当类中两个变量都相等时返回true
        System.out.println("s1.equals(s2):" + s1.equals(s2));
        System.out.println("com.compare(s1,s2):" + com.compare(s1,s2));
        // 此时两个元素都能添加进去,因为比较器com的compare方法判断两者不相等,和equals一样
        System.out.println("sortedSet()中元素个数:" + sortedSet.size());
    }
}
s1:CompEntity{first=1, second=1}
s2:CompEntity{first=1, second=3}
s1.equals(s2):false
com.compare(s1,s2):1
sortedSet()中元素个数:2

Comparable和Comparator区别

  1. 实现Comparable接口的类能够实现自然排序,其中自然排序方法由compareTo实现;
  2. 有自然排序方法,使用Arrays.sort和Collections.sort方法时无需指定比较器;
  3. 一般情况下,自然排序和equals方法尽量保持一致,因为不一致会导致有关集合类出现反常;
  4. Comparable和Comparator同时使用时,Comparator的优先级更高;
  5. Comparator比较器更加灵活,能够根据实际需要定义新的、临时的比较规则;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值