在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接口。