Comparable and
Comparator
一、Comparable
1. API解释(源码翻译神器电梯直达)
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与 e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。注意,null 不是任何类的实例,即使 e.equals(null) 返回 false,e.compareTo(null) 也将抛出 NullPointerException。
建议(虽然不是必需的)最好使自然排序与 equals 一致。这是因为在使用自然排序与 equals 不一致的元素(或键)时,没有显式比较器的有序集合(和有序映射表)行为表现“怪异”。尤其是,这样的有序集合(或有序映射表)违背了根据 equals 方法定义的集合(或映射表)的常规协定。
例如,如果将两个键 a 和 b 添加到没有使用显式比较器的有序集合中,使 (!a.equals(b) && a.compareTo(b) == 0),那么第二个 add 操作将返回 false(有序集合的大小没有增加),因为从有序集合的角度来看,a 和 b 是相等的。
实际上,所有实现 Comparable 的 Java 核心类都具有与 equals 一致的自然排序。java.math.BigDecimal 是个例外,它的自然排序将值相等但精确度不同的 BigDecimal 对象(比如 4.0 和 4.00)视为相等。
从数学上讲,定义给定类 C 上自然排序的关系式 如下:
{(x, y)|x.compareTo(y) <= 0}。
整体排序的 商 是:
{(x, y)|x.compareTo(y) == 0}。
它直接遵循 compareTo 的协定,商是 C 的 等价关系,自然排序是 C 的 整体排序。当说到类的自然排序 与 equals 一致 时,是指自然排序的商是由类的 equals(Object) 方法定义的等价关系。
{(x, y)|x.equals(y)}。
2. 使用方法(在1中提取)
int compareTo(T o)
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
参数: o - 要比较的对象。
利用当前对象和传入的目标对象进行比较:
若是当前对象比目标对象大,则返回1,那么当前对象会排在目标对象的后面
若是当前对象比目标对象小,则返回-1,那么当前对象会排在目标对象的后面
若是两个对象相等,则返回0
抛出:ClassCastException - 如果指定对象的类型不允许它与此对象进行比较。
3. 示例:
- Student类实现Comparable接口,重写CompareTo方法
public class Student implements Comparable<Student>{
private long id;
private String name;
private int age;
public Student(Long id,String name,int age){
this.id=id;
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
if(this.age>o.age){
return -1;//由高到底排序
} else if(this.age<o.age){
return 1;
}else {
if(this.id>o.id){
return 1;//由底到高排序
}else if(this.id<o.id){
return -1;
}else{
return 0;
}
}
}
}
- 测试代码
@Test
public void testComparable(){
Student student1=new Student(100001l,"lqh",18);
Student student2=new Student(100004l,"Jack Ma",21);
Student student3=new Student(100003l,"lqd",21);
Student student4=new Student(100002l,"MaHuaTeng",19);
Student students[]={student1,student2,student3,student4};
System.out.println("********************排序前*********************");
for(Student s:students){
System.out.println(s);
}
System.out.println("***************array********************");
Arrays.sort(students);
for(Student s:students){
System.out.println(s);
}
Student students1[]={student1,student2,student3,student4};
List<Student> list=Arrays.asList(students1);
System.out.println();
System.out.println("********************排序前*********************");
for(Student s:list){
System.out.println(s);
}
System.out.println("***************list(有序的,可以重复的)********************");
Collections.sort(list);
for(Student s:list){
System.out.println(s);
}
System.out.println();
Student students2[]={student1,student2,student3,student4};
System.out.println("**************set(无序的,不可重复的)********************");
System.out.println("使用自动排序的容器(TreeSet、TreeMap)");
Set<Student> set1=new TreeSet<>(Arrays.asList(students2));
for(Student s:set1){
System.out.println(s);
}
}
- 输出结果
********************排序前*********************
Student{id=100001, name='lqh', age=18}
Student{id=100004, name='Jack Ma', age=21}
Student{id=100003, name='lqd', age=21}
Student{id=100002, name='MaHuaTeng', age=19}
***************array********************
Student{id=100003, name='lqd', age=21}
Student{id=100004, name='Jack Ma', age=21}
Student{id=100002, name='MaHuaTeng', age=19}
Student{id=100001, name='lqh', age=18}
********************排序前*********************
Student{id=100001, name='lqh', age=18}
Student{id=100004, name='Jack Ma', age=21}
Student{id=100003, name='lqd', age=21}
Student{id=100002, name='MaHuaTeng', age=19}
***************list(有序的,可以重复的)********************
Student{id=100003, name='lqd', age=21}
Student{id=100004, name='Jack Ma', age=21}
Student{id=100002, name='MaHuaTeng', age=19}
Student{id=100001, name='lqh', age=18}
二、Comparator
1. API解释
强行对某个对象 collection 进行整体排序 的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
当且仅当对于一组元素 S 中的每个 e1 和 e2 而言,c.compare(e1, e2)==0 与 e1.equals(e2) 具有相等的布尔值时,Comparator c 强行对 S 进行的排序才叫做与 equals 一致 的排序。
当使用具有与 equals 不一致的强行排序能力的 Comparator 对有序 set(或有序映射)进行排序时,应该小心谨慎。假定一个带显式 Comparator c 的有序 set(或有序映射)与从 set S 中抽取出来的元素(或键)一起使用。如果 c 强行对 S 进行的排序是与 equals 不一致的,那么有序 set(或有序映射)将是行为“怪异的”。尤其是有序 set(或有序映射)将违背根据 equals 所定义的 set(或映射)的常规协定。
例如,假定使用 Comparator c 将满足 (a.equals(b) && c.compare(a, b) != 0) 的两个元素 a 和 b 添加到一个空 TreeSet 中,则第二个 add 操作将返回 true(树 set 的大小将会增加),因为从树 set 的角度来看,a 和 b 是不相等的,即使这与 Set.add 方法的规范相反。
注:通常来说,让 Comparator 也实现 java.io.Serializable 是一个好主意,因为它们在可序列化的数据结构(像 TreeSet、TreeMap)中可用作排序方法。为了成功地序列化数据结构,Comparator(如果已提供)必须实现 Serializable。
在算术上,定义给定 Comparator c 对给定对象 set S 实施强行排序 的关系式 为:
{(x, y) such that c.compare(x, y) <= 0}.
此整体排序的 商 (quotient) 为:
{(x, y) such that c.compare(x, y) == 0}.
它直接遵循 compare 的协定,商是 S 上的 等价关系,强行排序是 S 上的 整体排序。当我们说 c 强行对 S 的排序是 与 equals 一致 的时,意思是说排序的商是对象的 equals(Object) 方法所定义的等价关系:
{(x, y) such that x.equals(y)}.
2. 使用方法
compare(T o1, T o2):
如果返回的值小于零,则不交换两个o1和o2的位置;
如果返回的值大于零,则交换o1和o2的位置。
注意,不论在compare(o1, o2)中是如何实现的(第一种实现方式是 o1-02, 第二种实现方式是 o2 - o1),都遵循上述原则,即返回值小于零,则交换o1和o2的位置;返回值大于零,则不交换o1和o2的位置。 所以,如果采用第一种实现方式,即 o1 - o2, 那么将是升序排序。因为在原始排序中o1在o2的前边,如果o1小于o2,那么o1 - o2小于零,即返回值是小于零,但是小于零是不会交换o1和o2的位置的,所以o1依然排在o2的前边,是升序;如果o1大于o2,那么o1 - o2大于零,即返回值是大于零,大于零是要交换o1和o2的位置的,所以要改变原始排序中o1和o2的位置,那么依然是升序
3. 示例
- Dog类
public class Dog {
private long id;
private String name;
private int age;
//此处省略getter和setter方法
public Dog(Long id, String name, int age){
this.id=id;
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Dog{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 测试方法
@Test
public void testComparator(){
Dog dog=new Dog(20001l,"大黄",4);
Dog dog1=new Dog(20003l,"肉丝",3);
Dog dog2=new Dog(20002l,"翠花",5);
Dog dogs[]={dog,dog1,dog2};
System.out.println("***************排序前*****************");
for(Dog d:dogs){
System.out.println(d);
}
Arrays.sort(dogs, new Comparator<Dog>() {
@Override
public int compare(Dog o1, Dog o2) {
if(o1.getAge()>o2.getAge()){
return -1;//由高到底排序
} else if(o1.getAge()<o2.getAge()){
return 1;
}else {
if(o1.getId()>o2.getId()){
return 1;//由底到高排序
}else if(o1.getId()<o2.getId()){
return -1;
}else{
return 0;
}
}
}
});
System.out.println("***************排序后*****************");
for(Dog d:dogs){
System.out.println(d);
}
System.out.println();
Dog dogs1[]={dog,dog1,dog2};
List<Dog> list=Arrays.asList(dogs1);
System.out.println("***************排序前*****************");
for(Dog d:list){
System.out.println(d);
}
Collections.sort(list, new Comparator<Dog>() {
@Override
public int compare(Dog o1, Dog o2) {
if(o1.getAge()>o2.getAge()){
return 1;
} else if(o1.getAge()<o2.getAge()){
return -1;
}else {
if(o1.getName().hashCode()>o2.getName().hashCode()){
return -1;
}else if(o1.getName().hashCode()<o2.getName().hashCode()){
return 1;
}else{
return 0;
}
}
}
});
System.out.println("***************排序后*****************");
for(Dog d:list){
System.out.println(d);
}
}
- 输出结果
***************排序前*****************
Dog{id=20001, name='大黄', age=4}
Dog{id=20003, name='肉丝', age=3}
Dog{id=20002, name='翠花', age=5}
***************排序后*****************
Dog{id=20002, name='翠花', age=5}
Dog{id=20001, name='大黄', age=4}
Dog{id=20003, name='肉丝', age=3}
***************排序前*****************
Dog{id=20001, name='大黄', age=4}
Dog{id=20003, name='肉丝', age=3}
Dog{id=20002, name='翠花', age=5}
***************排序后*****************
Dog{id=20003, name='肉丝', age=3}
Dog{id=20001, name='大黄', age=4}
Dog{id=20002, name='翠花', age=5}
三、总结
- Comparator是java.util包下提供的接口,而Comparable是java.lang包提供的接口
- 都是Java中的内部比较器接口,都是用来实现对一个自定义的类进行排序
- comparable是需要比较的对象来实现接口。这样对象调用实现的方法来比较。对对象的耦合度高(需要改变对象的内部结构,破坏性大)。comparator相当于一通用的比较工具类接口。需要定制一个比较类去实现它,重写里面的compare方法,方法的参数即是需要比较的对象。对象不用做任何改变。解耦。