Java中自然排序和比較器排序詳解
前言
當(dāng)指執(zhí)行插入排序、希爾排序、歸并排序等算法時(shí),比較兩個(gè)對(duì)象“大小”的比較操作。我們很容易理解整型的 i>j 這樣的比較方式,但當(dāng)我們對(duì)多個(gè)對(duì)象進(jìn)行排序時(shí),如何比較兩個(gè)對(duì)象的“大小”呢?這樣的比較 stu1 > stu2 顯然是不可能通過編譯的。為了解決如何比較兩個(gè)對(duì)象大小的問題,JDK提供了兩個(gè)接口 java.lang.Comparable 和 java.util.Comparator 。
一、自然排序:java.lang.Comparable
Comparable 接口中只提供了一個(gè)方法: compareTo(Object obj) ,該方法的返回值是 int 。如果返回值為正數(shù),則表示當(dāng)前對(duì)象(調(diào)用該方法的對(duì)象)比 obj 對(duì)象“大”;反之“小”;如果為零的話,則表示兩對(duì)象相等。
下面是一個(gè)實(shí)現(xiàn)了 Comparable 接口的 Student 類:
public class Student implements Comparable {
private int id;
private String name;
public Student() {
super();
}
@Override
public int compareTo(Object obj) {
if (obj instanceof Student) {
Student stu = (Student) obj;
return id - stu.id;
}
return 0;
}
@Override
public String toString() {
return "<" + id + ", " + name + ">";
}
}
Student 實(shí)現(xiàn)了自然排序接口 Comparable ,那么我們是怎么利用這個(gè)接口對(duì)一組 Student 對(duì)象進(jìn)行排序的呢?我們?cè)趯W(xué)習(xí)數(shù)組的時(shí)候,使用了一個(gè)類來(lái)給整型數(shù)組排序: java.util.Arrays 。我們使用 Arrays 的 sort 方法來(lái)給整型數(shù)組排序。翻翻 API 文檔就會(huì)發(fā)現(xiàn), Arrays 里給出了 sort 方法很多重載形式,其中就包括 sort(Object[] obj) ,也就是說(shuō) Arryas 也能對(duì)對(duì)象數(shù)組進(jìn)行排序,排序過程中比較兩個(gè)對(duì)象“大小”時(shí)使用的就是 Comparable 接口的 compareTo 方法。
public class CompareTest {
public static void main(String[] args) {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
Student[] stus = new Student[4];
stus[0] = stu1;
stus[1] = stu4;
stus[2] = stu3;
stus[3] = stu2;
System.out.println(“Array: ” + Arrays.toString(stus));
Arrays.sort(stus);
System.out.println(“Sort: ” + Arrays.toString(stus));
}
}
Student 數(shù)組里添加元素的順序并不是按學(xué)號(hào) id 來(lái)添加的。調(diào)用了 Arrays.sort(stus) 之后,對(duì) Student 數(shù)組進(jìn)行排序,不管 sort 是使用哪種排序算法來(lái)實(shí)現(xiàn)的,比較兩個(gè)對(duì)象“大小”這個(gè)操作,它是肯定要做的。那么如何比較兩個(gè)對(duì)象的“大小”? Student 實(shí)現(xiàn)的 Comparable 接口就發(fā)揮作用了。 sort 方法會(huì)將待比較的那個(gè)對(duì)象強(qiáng)制類型轉(zhuǎn)換成 Comparable ,并調(diào)用 compareTo 方法,根據(jù)其返回值來(lái)判斷這兩個(gè)對(duì)象的“大小”。所以,在這個(gè)例子中排序后的原 Student 亂序數(shù)組就變成了按學(xué)號(hào)排序的 Student 數(shù)組。
但是我們注意到,排序算法和 Student 類綁定了, Student 只有一種排序算法。但現(xiàn)實(shí)社會(huì)不是這樣的,如果我們不想按學(xué)號(hào)排序怎么辦?假如,我們想按姓名來(lái)給學(xué)生排序怎么辦?我們只能修改 Student 類的 Comparable 接口的 compareTo 方法,改成按姓名排序。如果在同一個(gè)系統(tǒng)里有兩個(gè)操作,一個(gè)是按學(xué)號(hào)排序,另外一個(gè)是按姓名排序,這怎么辦?不可能在 Student 類體中寫兩個(gè) compareTo 方法的實(shí)現(xiàn)。這么看來(lái)Comparable就有局限性了。為了彌補(bǔ)這個(gè)不足,JDK 還為我們提供了另外一個(gè)排序方式,也就是下面要說(shuō)的比較器排序。
二、比較器排序:java.util.Comparator
上面我提到了,之所以提供比較器排序接口,是因?yàn)橛袝r(shí)需要對(duì)同一對(duì)象進(jìn)行多種不同方式的排序,這點(diǎn)自然排序 Comparable 不能實(shí)現(xiàn)。另外, Comparator 接口的一個(gè)好處是將比較排序算法和具體的實(shí)體類分離了。
翻翻 API 會(huì)發(fā)現(xiàn), Arrays.sort 還有種重載形式:sort(T[] a, Comparator<? super T> c) ,這個(gè)方法參數(shù)的寫法用到了泛型,我們還沒講到。我們可以把它理解成這樣的形式: sort(Object[] a, Comparator c) ,這個(gè)方法的意思是按照比較器 c 給出的比較排序算法,對(duì) Object 數(shù)組進(jìn)行排序。Comparator 接口中定義了兩個(gè)方法: compare(Object o1, Object o2) 和 equals 方法,由于 equals 方法所有對(duì)象都有的方法,因此當(dāng)我們實(shí)現(xiàn) Comparator 接口時(shí),我們只需重寫 compare 方法,而不需重寫 equals 方法。Comparator 接口中對(duì)重寫 equals 方法的描述是:“注意,不重寫 Object.equals(Object) 方法總是安全的。然而,在某些情況下,重寫此方法可以允許程序確定兩個(gè)不同的 Comparator 是否強(qiáng)行實(shí)施了相同的排序,從而提高性能。”。我們只需知道第一句話就OK了,也就是說(shuō),可以不用去想應(yīng)該怎么實(shí)現(xiàn) equals 方法,因?yàn)榧词刮覀儾伙@示實(shí)現(xiàn) equals 方法,而是使用Object類的 equals 方法,代碼依然是安全的。
那么我們來(lái)寫個(gè)代碼,來(lái)用一用比較器排序。還是用 Student 類來(lái)做,只是沒有實(shí)現(xiàn) Comparable 接口。由于比較器的實(shí)現(xiàn)類只用顯示實(shí)現(xiàn)一個(gè)方法,因此,我們可以不用專門寫一個(gè)類來(lái)實(shí)現(xiàn)它,當(dāng)我們需要用到比較器時(shí),可以寫個(gè)匿名內(nèi)部類來(lái)實(shí)現(xiàn) Comparator 。
下面是我們的按姓名排序的方法:
public void sortByName () {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
Student[] stus = new Student[4];
stus[0] = stu1;
stus[1] = stu4;
stus[2] = stu3;
stus[3] = stu2;
System.out.println("Array: " + Arrays.toString(stus));
Arrays.sort(stus, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Student && o2 instanceof Student) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
//return s1.getId() - s2.getId(); // 按Id排
return s1.getName().compareTo(s2.getName()); // 按姓名排
}
return 0;
}
});
System.out.println("Sorted: " + Arrays.toString(stus));
}
當(dāng)我們需要對(duì)Student按學(xué)號(hào)排序時(shí),只需修改我們的排序方法中實(shí)現(xiàn)Comparator的內(nèi)部類中的代碼,而不用修改 Student 類。
注意: 當(dāng)然,你也可以用 Student 類實(shí)現(xiàn) Comparator 接口,這樣Student就是(is a)比較器了(Comparator)。當(dāng)需要使用這種排序的時(shí)候,將 Student 看作 Comparator 來(lái)使用就可以了,可以將 Student 作為參數(shù)傳入 sort 方法,因?yàn)?Student is a Comparator 。但這樣的代碼不是個(gè)優(yōu)秀的代碼,因?yàn)槲覀冎允褂帽容^器(Comparator),其中有個(gè)重要的原因就是,這樣可以把比較算法和具體類分離,降低類之間的耦合。
TreeSet對(duì)這兩種比較方式都提供了支持,分別對(duì)應(yīng)著TreeSet的兩個(gè)構(gòu)造方法:
1、TreeSet():根據(jù)TreeSet中元素實(shí)現(xiàn)的 Comparable 接口的 compareTo 方法比較排序
2、TreeSet(Comparator comparator):根據(jù)給定的 comparator 比較器,對(duì) TreeSet 中的元素比較排序
當(dāng)向 TreeSet 中添加元素時(shí),TreeSet 就會(huì)對(duì)元素進(jìn)行排序。至于是用自然排序還是用比較器排序,就看你的 TreeSet 構(gòu)造是怎么寫的了。當(dāng)然,添加第一個(gè)元素時(shí)不會(huì)進(jìn)行任何比較, TreeSet 中都沒有元素,和誰(shuí)比去???
下面,分別給出使用兩種排序比較方式的 TreeSet 測(cè)試代碼:
/**
* 使用自然排序
* Student必須實(shí)現(xiàn)Comparable接口,否則會(huì)拋出ClassCastException
*/
public void testSortedSet3() {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
SortedSet set = new TreeSet();
set.add(stu1);
set.add(stu3); // 若Student沒有實(shí)現(xiàn)Comparable接口,拋出ClassCastException
set.add(stu4);
set.add(stu2);
set.add(stu4);
set.add(new Student(12, "Little"));
System.out.println(set);
}
/**
* 使用比較器排序
* Student可以只是個(gè)簡(jiǎn)單的Java類,不用實(shí)現(xiàn)Comparable接口
*/
public void testSortedSet3() {
Student stu1 = new Student(1, "Little");
Student stu2 = new Student(2, "Cyntin");
Student stu3 = new Student(3, "Tony");
Student stu4 = new Student(4, "Gemini");
SortedSet set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Student
&& o2 instanceof Student) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return s1.getName().compareTo(s2.getName());
}
return 0;
}
});
set.add(stu1);
set.add(stu3);
set.add(stu4);
set.add(stu2);
set.add(stu4);
set.add(new Student(12, "Little"));
System.out.println(set);
}
另外,介紹個(gè)工具類,java.util.Collections。注意,這不是Collection接口。Collections很像Arrays類。Arrays提供了一系列用于對(duì)數(shù)組操作的靜態(tài)方法,查找排序等等。Collections也提供了一系列這樣的方法,只是它是用于處理集合的,雖然Collections類和Collection接口很像,但是不要被Collections的名字給欺騙了,它不是只能處理Collection接口以及子接口的實(shí)現(xiàn)類,同樣也可以處理Map接口的實(shí)現(xiàn)類。
總結(jié)
Java中自然排序和比較器排序的介紹就到這里了,文章介紹的還是相對(duì)詳細(xì)的,希望能對(duì)大家的學(xué)習(xí)或者工作帶來(lái)一定的幫助,如果有疑問大家可以留言交流。
相關(guān)文章
Java實(shí)現(xiàn)的AES256加密解密功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)的AES256加密解密功能,結(jié)合完整實(shí)例形式分析了Java實(shí)現(xiàn)AES256加密解密功能的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-02-02
Java項(xiàng)目之java+springboot+ssm實(shí)現(xiàn)理財(cái)管理系統(tǒng)設(shè)計(jì)
這篇文章主要介紹了Java項(xiàng)目java+springboot+ssm實(shí)現(xiàn)理財(cái)管理系統(tǒng)設(shè)計(jì),使用了當(dāng)前較為流行的spring boot,spring,spring mvc,mybatis,shiro框架分頁(yè)處理使用了pagehelper進(jìn)行操作,需要的朋友可以參考一下2022-03-03
Java中冒泡排序的原生實(shí)現(xiàn)方法(正序與逆序)
這篇文章主要給大家介紹了關(guān)于Java中冒泡排序的原生實(shí)現(xiàn)方法(正序與逆序)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java方法參數(shù)是引用調(diào)用還是值調(diào)用?
Java方法參數(shù)是引用調(diào)用還是值調(diào)用?這是一個(gè)值得思考的問題。閱讀本文,找出答案2016-02-02
MyBatis多表關(guān)聯(lián)查詢的實(shí)現(xiàn)示例
本文主要介紹了MyBatis多表關(guān)聯(lián)查詢的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
詳解Vue響應(yīng)式的部分實(shí)現(xiàn)
響應(yīng)式,簡(jiǎn)單來(lái)說(shuō)當(dāng)數(shù)據(jù)發(fā)生變化時(shí),對(duì)數(shù)據(jù)有依賴的代碼會(huì)重新執(zhí)行。這篇文章主要為大家介紹了Vue中響應(yīng)式的部分實(shí)現(xiàn),感興趣的可以了解一下2022-12-12
netflix.discovery.shared.transport.TransportException:Cannot
這篇文章主要介紹了netflix.discovery.shared.transport.TransportException:Cannot execute request on any known server報(bào)錯(cuò)問題及解決方法,感興趣的朋友一起看看吧2023-09-09

