不像前幾個章節提到的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]
}
}
參考文件: