目录
- 1. 自然排序:java.lang.Comparable
- 2. 定制排序:java.util.Compartor
- 3. 比较方法的返回值正负与升序、降序的关系
- 4. Comparable接口和Comparator接口的区别
在Java中经常会涉及到多个对象的排序问题,那么就涉及到对象之间的比较。
Java实现对象排序的方式有两种:
自然排序:java.lang.Comparable
定制排序:ava.util.Comparator
1. 自然排序:java.lang.Comparable
-
Comparable
接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。Comparable
接口中只有一个抽象方法:int compareTo(Object o);
。 -
实现
Comparable
的类必须实现compareTo(Object o)
方法,两个对象即通过compareTo(Object o)
方法的返回值来比较大小。- 如果当前对象 this 大于形参对象 o ,则返回正整数,
- 如果当前对象 this 小于形参对象 o ,则返回负整数,
- 如果当前对象 this 等于形参对象 o ,则返回零。
-
实现
Comparable
接口的类的对象数组(和有序集合)可以通过 Arrays.sort(和 Collections.sort )进行自动排序。 -
Comparable的典型实现:(默认都是从小到大排序)
String、包装类等实现了Comparable接口,重写了compareTo(obj)方法
String:按照字符串中字符的Unicode值进行比较
数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
Character:按照字符的Unicode值来进行比较
Boolean:true 对应的包装类实例大于 false 对应的包装类实例
Date、Time等:后面的日期时间比前面的日期时间大代码示例:
String[] arr = new String[]{"AA","cc","ac","dd","aa","FF","ff"}; Arrays.sort(arr); System.out.println(Arrays.toString(arr)); // [AA, FF, aa, ac, cc, dd, ff]
-
对于自定义类来说,如果需要排序,我们可以让自定义类实现
Comparable
接口,重写compareTo(Object o)
方法。在compareTo(Object o)
方法中指明如何排序。定义学生类:
public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
测试类:
public class ComparableTest { public static void main(String[] args) { Student[] students = new Student[5]; students[0] = new Student("rose", 16); students[1] = new Student("jack", 18); students[2] = new Student("mark", 16); students[3] = new Student("john", 16); students[4] = new Student("lily", 17); Arrays.sort(students); for (int i = 0; i < students.length; i++) { System.out.println(students[i]); } } }
发现程序出现了类型转换异常:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ggMQjfNv-1598369086597)(img/Comparable.png)]
原因:对于自定义类来说,如果需要排序,自定义类必须实现
Comparable
接口,重写compareTo(Object o)
方法。在compareTo(Object o)
方法中指明如何排序。修改Student类,实现
Comparable
接口,指定按学生年龄升序排序:public class Student implements Comparable { ... @Override public int compareTo(Object o) { Student student = (Student) o; if (this.age < student.age) { return -1; } if (this.age > student.age) { return 1; } return 0; } }
运行结果:
Student{name='mark', age=15}
Student{name='rose', age=16}
Student{name='john', age=16}
Student{name='lily', age=17}
Student{name='jack', age=18}如果要按学生年龄降序排序,则修改compareTo方法:当前对象的年龄小于参数对象时,返回正整数;当前对象大于参数对象时,返回负整数:
@Override public int compareTo(Object o) { Student student = (Student) o; if (this.age < student.age) { return 1; } if (this.age > student.age) { return -1; } return 0; }
2. 定制排序:java.util.Compartor
-
当元素的类型没有实现
java.lang.Comparable
接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator
接口的实现类来排序。 -
Comparator
接口中只有两个抽象方法int compare(Object o1, Object o2);
、boolean equals(Object obj);
,Comparator
接口实现类默认继承了Object
类的equals
方法,即间接实现了equals
方法,因此只需实现int compare(Object o1, Object o2)
即可。 -
可以将
Comparator
接口实现类传递给 sort 方法(如 Arrays.sort 或 Collections.sort),从而允许在排序顺序上实现精确控制。 -
重写
int compare(Object o1, Object o2)
方法,比较o1和o2的大小:如果要按照升序排序,
则 o1小于o2返回负整数,o1与o2相等返回0,01大于02返回正整数如果要按照降序排序
则 o1小于o2返回正整数,o1与o2相等返回0,01大于02返回负整数代码演示:
定义Student类,无需实现Comparable接口:
public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
测试类:向sort方法中传入待排序的数组对象和Comparator接口实现类,指定按学生年龄升序排序:
public class ComparableTest { public static void main(String[] args) { Student[] students = new Student[5]; students[0] = new Student("rose", 16); students[1] = new Student("jack", 18); students[2] = new Student("mark", 15); students[3] = new Student("john", 16); students[4] = new Student("lily", 17); Arrays.sort(students, new Comparator() { @Override public int compare(Object o1, Object o2) { Student s1 = (Student) o1; Student s2 = (Student) o2; //下面的代码可简化为 return s1.getAge() - s2.getAge(); if (s1.getAge() < s2.getAge()) { return -1; } if (s1.getAge() > s2.getAge()) { return 1; } return 0; } }); for (int i = 0; i < students.length; i++) { System.out.println(students[i]); } } }
运行结果:
Student{name='mark', age=15}
Student{name='rose', age=16}
Student{name='john', age=16}
Student{name='lily', age=17}
Student{name='jack', age=18}如果要按学生年龄降序排序,则修改compare方法:当前对象的年龄小于参数对象时,返回正整数;当前对象大于参数对象时,返回负整数:
@Override public int compare(Object o1, Object o2) { Student s1 = (Student) o1; Student s2 = (Student) o2; //下面的代码可简化为 return s2.getAge() - s1.getAge(); if (s1.getAge() < s2.getAge()) { return 1; } if (s1.getAge() > s2.getAge()) { return -1; } return 0; }
按年龄降序排序,年龄相同时按姓名首字母升序排序:
@Override public int compare(Object o1, Object o2) { Student s1 = (Student) o1; Student s2 = (Student) o2; //第一条排序规则:年龄降序 int result = s2.getAge() - s1.getAge(); //第二条排序规则:年龄相同时,姓名首字母升序 if (result == 0) { result = s1.getName().charAt(0) - s2.getName().charAt(0); } return result; }
运行结果:
Student{name='jack', age=18}
Student{name='lily', age=17}
Student{name='john', age=16}
Student{name='rose', age=16}
Student{name='mark', age=15}
3. 比较方法的返回值正负与升序、降序的关系
从前面已经知道:如果要按照升序排序,则 o1小于o2返回负整数,o1与o2相等返回0,01大于02返回正整数
如果要按照降序排序,则 o1小于o2返回正整数,o1与o2相等返回0,01大于02返回负整数
那么,对元素升序、降序排序时调整元素位置与接口中返回值的正负有什么关系呢?
测试环境:JDK8
原数组元素顺序:
Student{name='rose', age=16}
Student{name='jack', age=18}
Student{name='mark', age=15}
Student{name='john', age=16}
Student{name='lily', age=17}
- 在compare方法中,不管o1、o2对象的内容,统一返回正整数:
@Override
public int compare(Object o1, Object o2) {
return 1;
}
运行结果发现,数组元素顺序未改变。
- 在compare方法中,不管o1、o2对象的内容,统一放回负整数,运行结果发现,数组元素倒置。
- 在compare方法中,不管o1、o2对象的内容,统一放回0,运行结果发现,数组元素顺序未改变。
在comparable接口中及集合中的运行结果一致。
从而得出结论:在调用 compare(Object o1, Object o2)
方法时,会把两个元素中索引较小的元素赋值给o2,索引较大的元素赋值给o1。Comparable接口中compareTo(Object o)
方法的调用者是两个元素中索引较大的元素,参数对象 o 则是索引较小的元素。(JDK版本不同,对应关系可能不同)。通过debug调试或查看源码也能得出这个结论。
比如,对于数组{5, 10, 20, 15},比较前两个元素调用 compare(Object o1, Object o2)
方法时,会把5赋值给o2,把10赋值给o1。按升序排序时,若o1 > o2,则返回正整数,不调整元素位置;若o1 == o2,则返回0,不调整元素位置;若o1 < o2,则返回负整数,调整元素位置。按降序排序时,若o1 > o2,则返回负整数,需要调整元素位置;若o1 == o2,则返回0,不调整元素位置;若o1 < o2,则返回正整数,不调整元素位置。因此,不管是升序还是降序,只有在返回负整数时,才会调整元素的位置。
综上,排序时是否需要调整元素位置是由对象o1、o2与数组中元素的对应关系 和 比较方法返回值的正负共同决定的。
4. Comparable接口和Comparator接口的区别
只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。
用Comparator 的好处是不需要修改源代码, 而是在待比较对象的类的外部实现一个比较器, 当某个自定义的对象需要作比较的时候,把待比较对象和比较器一起传递过去就可以实现排序功能。
此外,像String类、包装类等JDK内置类实现了Comparable接口默认是升序排序,如果要降序排序或指定其他排序规则只能使用Comparator接口。