iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
Software Development

深入淺出Java 30天系列 第 16

Day 16: 考慮實作Comparable

  • 分享至 

  • xImage
  •  

不像前幾個章節提到的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

  • 回傳正整數、負整數或0,代表大於、小於或等於要比較的物件。如果兩個物件型別不同無法compare,可以丟出ClassCastException 。另外,如果沒有實作Comparable ,卻使用collection架構的sorting功能,也會拋出ClassCastException
  • 可以使用Math.signum()確認結果有沒有一致,兩個物件互相compare之後的Math.signum()結果,取絕對值之後需相等,例如Math.signum(x.compareTo(y)) == -Math.signum(y.compareTo(x))
  • 須滿足transitive的特性,x.compareTo(y) > 0 && y.compareTo(z) > 0這個條件成立的話,x.compareTo(z) > 0這個條件也要成立。
  • 如果用兩個物件compare的結果相等,那麼兩個物件同時對第三個物件用Math.signum()確認比較的結果也要相等。也就是若x.compareTo(y)= 0Math.signum(x.compareTo(z)) == Math.signum(y.compareTo(z))

實作compareTo()之後,TreeSetTreeMap就會使用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);
        }
    }
}


最後,需要特別注意的是,應該盡量讓compareToequals的結果一致,如果這兩個結果不一致,雖然不會讓程式compile error或runtime error,但是會讓不同collection的結果不一樣,debug上容易造成困擾,舉例來說,TreeSet使用compareTo認定item有沒有存在Set裡面,需不需要加進Set,但是HashSet是使用hashCodeequals進行比較,而BigDecimal這個類別的compareToequals結果不一致,造成有些情境的結果不一樣。舉例來說,1.0和1.00在BigDecimalcompareTo是一樣,但是在equals會認為不一樣,所以用TreeSetHashSet結果不一致。

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]
    }
}

參考文件:


上一篇
Day 15: 總是覆寫toString
下一篇
Day 17: 最小化類別、方法和欄位的存取權限
系列文
深入淺出Java 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言