目录
- 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
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接口。