不像前幾個章節提到的equals()、hashCode()和clone(),所有類別所繼承的Object已有實作那些method,Object並沒有實作compareTo(),如果物件想要使用compareTo()的功能,並且用在collection的排序功能上,就必須實作Comparable,跟使用hash code排序的差別是,Comparable的排序是自然排序,排序的依據會根據人類的理解進行排序,例如年齡或價錢,hash code是電腦為了快速查找資料,編碼過後進行的排序。
實作Comparable並不難,只要implements Comparable<T>,並且實作compareTo即可,就像下面的範例一樣。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Fruit implements Comparable<Fruit> {
private String name;
private double price;
public Fruit(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public int compareTo(Fruit other) {
// 按價格升序排序
return Double.compare(this.price, other.price);
}
}
實作compareTo()時,需符合下面幾條規則,就可以完成實作compareTo。
ClassCastException 。另外,如果沒有實作Comparable ,卻使用collection架構的sorting功能,也會拋出ClassCastException。Math.signum()確認結果有沒有一致,兩個物件互相compare之後的Math.signum()結果,取絕對值之後需相等,例如Math.signum(x.compareTo(y)) == -Math.signum(y.compareTo(x))。x.compareTo(y) > 0 && y.compareTo(z) > 0這個條件成立的話,x.compareTo(z) > 0這個條件也要成立。Math.signum()確認比較的結果也要相等。也就是若x.compareTo(y)= 0則Math.signum(x.compareTo(z)) == Math.signum(y.compareTo(z))。實作compareTo()之後,TreeSet和TreeMap就會使用compareTo()的結果進行排序,當使用Collections.sort()和Arrays.sort()時,也會使用compareTo()進行排序,甚至是PriorityQueue也可以設計使用Comparable進行排序。下面範例是使用Collections.sort()進行排序,可以看到執行結果會依照compareTo()裡面用的price進行排序。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Fruit implements Comparable<Fruit> {
private String name;
private double price;
public Fruit(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public int compareTo(Fruit other) {
// 按價格升序排序
return Double.compare(this.price, other.price);
}
@Override
public String toString() {
return "Fruit{name='" + name + "', price=" + price + "}";
}
public static void main(String[] args) {
List<Fruit> fruits = new ArrayList<>();
fruits.add(new Fruit("Apple", 1.50));
fruits.add(new Fruit("Banana", 0.75));
fruits.add(new Fruit("Cherry", 2.00));
Collections.sort(fruits);
for (Fruit fruit : fruits) {
System.out.println(fruit);
}
}
}

最後,需要特別注意的是,應該盡量讓compareTo和equals的結果一致,如果這兩個結果不一致,雖然不會讓程式compile error或runtime error,但是會讓不同collection的結果不一樣,debug上容易造成困擾,舉例來說,TreeSet使用compareTo認定item有沒有存在Set裡面,需不需要加進Set,但是HashSet是使用hashCode和equals進行比較,而BigDecimal這個類別的compareTo和equals結果不一致,造成有些情境的結果不一樣。舉例來說,1.0和1.00在BigDecimal的compareTo是一樣,但是在equals會認為不一樣,所以用TreeSet和HashSet結果不一致。
import java.math.BigDecimal;
import java.util.TreeSet;
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(new BigDecimal("1.0"));
treeSet.add(new BigDecimal("1.00"));
System.out.println("TreeSet: " + treeSet); // 只會顯示一個元素 [1.0]
HashSet<BigDecimal> hashSet = new HashSet<>();
hashSet.add(new BigDecimal("1.0"));
hashSet.add(new BigDecimal("1.00"));
System.out.println("HashSet: " + hashSet); // 會顯示兩個元素 [1.0, 1.00]
}
}
參考文件: