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具有比较功能,可以对对象集合施加一个顺序,可以用作如下场景:
- Collections.sort和Arrays.sort精确控制排序顺序;
- 控制已有的如sorted sets或sorted maps等数据结构的内部元素顺序;
- 给没有自然顺序的集合提供排序方式;
同样的,和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区别
- 实现Comparable接口的类能够实现自然排序,其中自然排序方法由compareTo实现;
- 有自然排序方法,使用Arrays.sort和Collections.sort方法时无需指定比较器;
- 一般情况下,自然排序和equals方法尽量保持一致,因为不一致会导致有关集合类出现反常;
- Comparable和Comparator同时使用时,Comparator的优先级更高;
- Comparator比较器更加灵活,能够根据实际需要定义新的、临时的比较规则;