天天看點

Java排序之Comparable接口和Comparator接口的比較和應用示例

Java排序之Comparable接口和Comparator接口的比較和應用示例

一、其實很簡單,單從字面了解就基本明白

1. Comparable:“可比較的”,(欲參與比較的對象對應的元素類需實作Comparable接口)

使用這種政策來比較時,兩個對象(這裡兩個對象是指一個類的兩個不同執行個體)本身必須是“可比較的”,比較的标準由對象所在的類來定義,這種可比較的能力是對象本身固有的,是以不需要第三方參與就可以完成比較。要使得兩個對象本身是可比較的,那麼對象所在的類必須實作Comparable接口才可以。其compareTo()方法隻要一個參數,因為這裡隻有“你”“我”的關系,沒有第三方。

比如,兩個人要比較身高,分辨高矮是人類固有的能力,兩個人隻要站到一起就能分出誰高誰矮。

2. Comparator:“比較器”

使用這種政策來比較時,如何進行比較和兩個對象本身無關,而是由第三者(即比較器)來完成的。第三方比較器類需要另外專門設計:隻要實作Comparator接口,任何一個類(對象)都可能成為一個“比較器”,但比較器并不是比較自己的執行個體,而是比較其它類的兩個不同對象,比較器在這裡充當“仲裁者”的角色,這也就是為什麼compare()方法需要兩個參數。

比如,兩個人要比較誰智商更高,靠他們自身無法進行,這時要借助一個比較器(比如,智商測試題)。

注: Comparable和Comparator這兩個接口和集合接口(Collection)本身無關,但通常和集合内的元素有關,因為集合的排序要用到這兩個排序接口中的方法(二選其一)。一個類的多個執行個體要想實作排序,必須實作Comparable,或者提供相應的Comparator才能使用Collections.sort()進行排序。

這裡我們需要專門說明一下:java.util.Collections,是不屬于java的集合架構的,它是一個集合的工具類,它包含專門操作集合(Collection)的靜态方法,如排序、拷貝、比對查找等等,比如我們常用它的排序方法:Collections.sort(List), Collections.sort(List, Comparator)。

二、代碼示例

1. 假如你維護着一個簡單的員工資料庫,每個員工是一個Employee類的執行個體。

Employee類可定義為:

public class Employee {	
	private String num;
	private String name;
	private int age;
	private int salary;	
	public Employee(String num, String name) {
		this.num = num;
		this.name = name;
	}	
	public void setName(String newNum) {
		num = newNum;
	}

	public void setAge(int newAge) {
		age = newAge;
	}
	public void setSalary(int newSalary) {
		salary = newSalary;
	}
	public String getNum() {
		return num;
	}
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}
	public int getSalary() {
		return salary;
	}
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("Employee Information:");
		sb.append("\n");
		sb.append("Number:");
		sb.append(getNum());
		sb.append("\n");
		sb.append("Name:");
		sb.append(getName());
		sb.append("\n");
		sb.append("Age:");
		sb.append(getAge());
		sb.append("\n");
		sb.append("Salary:");
		sb.append(getSalary());
		sb.append("\n");
		return sb.toString();
	}
}
           

EmployeeDatabase類建立Employee類的執行個體,并把它們存入集合:

import java.util.*;

public class EmployeeDatabase {
	
	public static void main(String[] args) {
		
		List<Employee> allEmployees = new ArrayList<Employee>();
		
		Employee employee1 = new Employee("AAA", "Barack Omaba");
		employee1.setAge(50);
		employee1.setSalary(9999);
		allEmployees.add(employee1);
		
		Employee employee2 = new Employee("BBB", "George Bush");
		employee2.setAge(60);
		employee2.setSalary(5999);
		allEmployees.add(employee2);
		
		System.out.println(allEmployees);

	}

}
           

2. 現在,你需要檢索所有員工,并讓他們按一定順序顯示(比如按年齡遞增),這時需要用到Collections.sort()方法。

Collections.sort()有兩種政策:一種是讓集合中元素所屬類本身實作Comparable接口,另一種是使用使用者另外提供的比較器(即Comparator)。

2.1 使用第一種政策時,必須修改元素類的定義, 讓它實作Comparable接口,是以,你必須把Employee類修改為:

public class Employee implements Comparable<Employee> {
	
	public int compareTo(Employee another) {
		return getAge() - another.getAge();
	}
	
	// 其餘部分不變

}
           

說明一下,因為 compareTo()方法約定:本對象大于另一個對象時,傳回大于0的整數,小于時傳回小于0的整數,等于時傳回0。是以,可以直接傳回兩者年齡的差,來實作按年齡比較。 這樣就可以在main()方法中使用Collections.sort(allEmployees);來對員工按年齡排序了。

但是,這種排序是非常不靈活的:

第一,需要修改集合元素類Employee,而很多情況下,我們沒有辦法修改公共的類。

第二,沒有辦法實作多種方式排序,如按編号,按姓名,按薪水等等。

2.2 這時需要使用另一種政策,建立一個比較器類,其需要實作Comparator接口。Comparator使用其compare()方法傳回的整數來比較兩個對象,規則和compareTo()一樣。

如同樣實作年齡比較, 使用Comparator時,無需修改Employee類,可以在排序的時候定義相應的比較器。使用Collections.sort()方法的另一個版本:

Collections.sort(allEmployees, new Comparator<Employee>() {
	public int compare(Employee one, Employee another) {
		return one.getAge() - another.getAge();
	}
});
           

這裡代碼使用了 匿名内部類,實際上 相當于先定義一個比較器類,如:

class EmployeeComparator implements Comparator<Employee> {
	
	public int compare(Employee one, Employee another) {
		return one.getAge() - another.getAge();
	}
	
}
           

再使用:

Collections.sort(allEmployees, new EmployeeComparator());
           

可以看到,比較器完全獨立于元素類Employee,是以可以非常友善地修改排序規則。

你還可以定義一系列比較器,供排序時選擇使用,如:

// 按薪水升序
class EmployeeSalaryAscendingComparator implements Comparator<Employee> {
	
	public int compare(Employee one, Employee another) {
		return one.getSalary() - another.getSalary();
	}
	
}

// 按薪水降序
class EmployeeSalaryDescendingComparator implements Comparator<Employee> {
	
	public int compare(Employee one, Employee another) {
		return another.getSalary() - one.getSalary();
	}
	
}
           

相應的使用方法如:

Collections.sort(allEmployees, new EmployeeSalaryAscendingComparator());
Collections.sort(allEmployees, new EmployeeSalaryDescendingComparator());
           

等等....

使用Comparator時,元素類無需實作Comparable,是以我們保持最初版本的Employee,但實際應用中,可以用Comparable的compareTo()方法來定義預設排序方式,用Comparator定義其他排序方式。

三、總結-Camparable接口和Comparator接口的比較

1. Camparable的使用方式是:在欲比較的類内部定義實作compareTo()方法,然後再使用Collections. sort()來實作排序,而Comparator是在欲比較的類外部實作的排序。是以,如想實作排序,就需要在在類内實作Comparable接口的方法compareTo() 或類外定義實作Comparator接口的方法compare()。

Comparable是一個對象本身就已經支援自比較所需要實作的接口(如String ,Integer自己就可以完成比較大小操作),而Comparator是一個專用的比較器,當這個對象不支援自比較或者自比較函數不能滿足你的要求時,你可以寫一個比較器來完成兩個對象之間大小的比較。

2.一個類實作了Camparable接口則表明這個類的對象之間是可以互相比較的,這個類對象組成的集合就可以直接使用sort()方法排序。一般我們寫的bean都要都要實作這一接口,這也是标準javabean的規範。

3.Comparator可以看成一種算法的實作,将算法和資料分離,Comparator也可以在下面兩種場景下使用:

3.1 類的設計師沒有考慮到比較問題而沒有實作Camparable接口,我們可以通過Comparator來實作排序而不必改變類本身。比如,你使用了一個第三方的類,但這個第三方類麼有實作Camparable接口,而你又不能改它的代碼,另外,類一旦寫好後是不允許修改的,但 可以拓展,這時候隻能使用Comparator來實作排序。

3.2 可以使用多種排序标準,比如升序、降序等。

比如,實作Comparable隻能定義一種比較方法即compareTo(),但是有時候會對一個集合進行不同的排序方法,此時就可以提供别各種各樣的Comparator來對集合排序,而對于要排序的元素不需要更改,是以我覺得Comparator提供了更多的靈活性。