首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 比較與排序完全指南:Comparable 與 Comparator 深度解析與實戰

     編輯:Java 相關知識

兩大比較介面核心差異

特性Comparable<T>Comparator<T>
套件java.langjava.util
核心方法int compareTo(T o)int compare(T o1, T o2)
設計定位類別的「自然順序」,內建於類別內部外部比較策略,算法與資料分離 (策略模式)
綁定性與具體類別強綁定,一個類別僅能有一種自然順序靈活多變,可為同一類別定義多種排序規則
適用場景類別設計時即確定預設排序邏輯1. 類別未實作 Comparable
2. 需要多種排序方式 (升序/降序/自訂)
支援的集合TreeSet、TreeMap、Collections.sort、Arrays.sort同上,但需顯式傳入 Comparator 實例

Comparable 介面深度解析

介面定義

public interface Comparable<T> {
    int compareTo(T o);
}

回傳值規約

  • 負值:當前物件 小於 參數物件 (排在前面)
  • 零:兩物件 相等 (排序位置相同)
  • 正值:當前物件 大於 參數物件 (排在後面)

JDK 內建實作 Comparable 的類別 (部分)

實作範例:Person 類別 (年齡主鍵、姓名次鍵升序)

package com.zj.sort.comparable;

public class Person implements Comparable<Person> {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public int compareTo(Person person) {
        int ageDiff = this.age - person.getAge();
        if (ageDiff != 0) return ageDiff;           // 年齡不同,按年齡排序
        return this.name.compareTo(person.name);    // 年齡相同,按姓名字典序排序
    }

    // 必須配合實作 equals/hashCode,確保 TreeSet/TreeMap 正確運作
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Person)) return false;
        Person p = (Person) o;
        return age == p.age && name.equals(p.name);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 37 * result + age;
        result = 37 * result + name.hashCode();
        return result;
    }

    // Getters...
    public int getAge() { return age; }
    public String getName() { return name; }

    @Override
    public String toString() {
        return age + "{" + name + "}";
    }
}

Comparable 應用驗證

Arrays.sort()

Person[] ps = { new Person(20, "Tom"), new Person(20, "Jeff"),
                new Person(30, "Mary"), new Person(20, "Ada"),
                new Person(40, "Walton"), new Person(61, "Peter"),
                new Person(20, "Bush") };
Arrays.sort(ps);
// 結果:[20{Ada}, 20{Bush}, 20{Jeff}, 20{Tom}, 30{Mary}, 40{Walton}, 61{Peter}]

Collections.sort()

Collections.sort(Arrays.asList(ps));
// 結果同上

TreeSet (自動排序、去重)

TreeSet<Person> set = new TreeSet<>();
set.add(new Person(20, "Tom"));
// ... 加入所有元素
// 結果:[20{Ada}, 20{Bush}, 20{Jeff}, 20{Tom}, 30{Mary}, 40{Walton}, 61{Peter}]

TreeMap (Key 自動排序)

TreeMap<Person, String> map = new TreeMap<>();
map.put(new Person(20, "Tom"), "Tom");
// ...
// 結果:{20{Ada}=Ada, 20{Bush}=Bush, 20{Jeff}=Jeff, 20{Tom}=Tom, 30{Mary}=Mary, 40{Walton}=Walton, 61{Peter}=Peter}

Comparator 介面深度解析

介面定義

public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);  // 通常不需覆寫,預設 Object.equals 即可
}

設計模式:策略模式

Comparator 將比較算法封裝為獨立物件,符合「開放封閉原則」——新增排序規則無需修改原類別。

Person 類別 (不實作 Comparable,純資料載體)

package com.zj.sort.comparator;

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    // 建構子、Getters、equals、hashCode、toString...
}

比較器實作:依 FirstName 優先排序

public class FirstNameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        String fn1 = p1.getFirstName().toUpperCase();
        String fn2 = p2.getFirstName().toUpperCase();
        if (fn1.equals(fn2)) {
            return p1.getLastName().toUpperCase().compareTo(p2.getLastName().toUpperCase());
        }
        return fn1.compareTo(fn2);
    }
}

比較器實作:依 LastName 優先排序

public class LastNameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        String ln1 = p1.getLastName().toUpperCase();
        String ln2 = p2.getLastName().toUpperCase();
        if (ln1.equals(ln2)) {
            return p1.getFirstName().toUpperCase().compareTo(p2.getFirstName().toUpperCase());
        }
        return ln1.compareTo(ln2);
    }
}

Comparator 應用驗證

Person[] ps = { new Person(20, "Tom", "A"), new Person(20, "Jeff", "A"),
                new Person(30, "Mary", "A"), new Person(20, "Ada", "B"),
                new Person(40, "Walton", "B"), new Person(61, "Peter", "B"),
                new Person(20, "Bush", "B") };

// FirstName 優先排序
Arrays.sort(ps, new FirstNameComparator());
// [20{Ada B}, 20{Bush B}, 20{Jeff A}, 30{Mary A}, 61{Peter B}, 20{Tom A}, 40{Walton B}]

// LastName 優先排序
Arrays.sort(ps, new LastNameComparator());
// [20{Jeff A}, 30{Mary A}, 20{Tom A}, 20{Ada B}, 20{Bush B}, 61{Peter B}, 40{Walton B}]

TreeSet 搭配 Comparator

TreeSet<Person> firstNameSet = new TreeSet<>(new FirstNameComparator());
firstNameSet.addAll(Arrays.asList(ps));
// [20{Ada B}, 20{Bush B}, 20{Jeff A}, 30{Mary A}, 61{Peter B}, 20{Tom A}, 40{Walton B}]

TreeSet<Person> lastNameSet = new TreeSet<>(new LastNameComparator());
lastNameSet.addAll(firstNameSet);
// [20{Jeff A}, 30{Mary A}, 20{Tom A}, 20{Ada B}, 20{Bush B}, 61{Peter B}, 40{Walton B}]

TreeMap 搭配 Comparator

TreeMap<Person, String> firstNameMap = new TreeMap<>(new FirstNameComparator());
firstNameMap.put(new Person(20, "Tom", "A"), "Tom A");
// ...
// {20{Ada B}=Ada B, 20{Bush B}=Bush B, 20{Jeff A}=Jeff A, 30{Mary A}=Mary A, 61{Peter B}=Peter B, 20{Tom A}=Tom A, 40{Walton B}=Walton B}

進階技巧與最佳實踐

1. Java 8+ Lambda 簡寫 Comparator


// 靜態導入:import static java.util.Comparator.*;

Comparator<Person> byAge = comparing(Person::getAge);
Comparator<Person> byAgeThenName = comparing(Person::getAge).thenComparing(Person::getName);
Comparator<Person> byNameDesc = comparing(Person::getName, reverseOrder());

// 多欄位複雜排序
Arrays.sort(ps, comparing(Person::getLastName)
                    .thenComparing(Person::getFirstName)
                    .thenComparingInt(Person::getAge));

2. 反轉順序

Collections.sort(list, Collections.reverseOrder());           // 自然順序反轉
Collections.sort(list, comparator.reversed());                // Comparator 反轉
Arrays.sort(ps, Comparator.comparing(Person::getAge).reversed());

3. 空值處理

Comparator<Person> nullsFirst = Comparator.nullsFirst(comparing(Person::getName));
Comparator<Person> nullsLast = Comparator.nullsLast(comparing(Person::getName));

4. 避免減法溢位 (比較整數)

// 錯誤寫法:可能溢位
return this.age - other.age;

// 正確寫法
return Integer.compare(this.age, other.age);
// 或 Java 7+
return this.age < other.age ? -1 : (this.age > other.age ? 1 : 0);

5. 一致性原則

  • compareTo 與 equals 應保持一致:(x.compareTo(y) == 0) == x.equals(y)
  • 違反此原則會導致 TreeSet/TreeMap 行為異常 (如無法正確移除元素)

總結:如何選擇?

  1. 類別有明確「自然順序」: 實作 Comparable (如數值、日期、字串、ID 排序)
  2. 需要多種排序方式: 撰寫多個 Comparator 實作類別
  3. 無法修改原類別 (第三方庫): 唯有使用 Comparator
  4. Java 8+ 專案: 優先使用 Comparator.comparing... 鏈式 API,簡潔且可讀性高
  5. 效能敏感場景: 避免在比較器中建立大量臨時物件 (如 toUpperCase()),可預先正規化資料
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類別自然排序規則
BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short數值大小
CharacterUnicode 數值大小
String字元 Unicode 值逐字比較 (字典序)
Date, LocalDate, LocalDateTime時間先後順序