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

深入掌握 Java Collections API:Comparable、Comparator 與排序進階

     編輯:Java 相關知識

為什麼 List 能自動排序?

Collections.sort(list) 能直接對 String、Date、包裝類型排序,是因為它們實現了 java.lang.Comparable<T> 接口,提供了自然頩序。JDK 中實現 Comparable 的核心類及其排序規則:

類別自然頩序
Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal帶符號數值升序
Character無符號數值升序 (Unicode 碼點)
String字典序 (逐字符比較 Unicode 值)
Date時間先後升序
File系統相關路徑字典序
CollationKey特定 Locale 字典序

注意:不同類型之間不可相互比較 (拋 ClassCastException),且列表元素必須相互可比 (mutually comparable)。

自定義類實現 Comparable:Name 類完整範例

public final class Name implements Comparable<Name> {
    private final String firstName, lastName; // 不變類

    public Name(String firstName, String lastName) {
        if (firstName == null || lastName == null)
            throw new NullPointerException();
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() { return firstName; }
    public String getLastName()  { return lastName; }

    // equals/hashCode 契約:放入 Set/Map 鍵必須重寫
    @Override public boolean equals(Object o) {
        if (!(o instanceof Name)) return false;
        Name n = (Name) o;
        return firstName.equals(n.firstName) && lastName.equals(n.lastName);
    }
    @Override public int hashCode() {
        return 31 * firstName.hashCode() + lastName.hashCode();
    }
    @Override public String toString() { return firstName + " " + lastName; }

    // 核心:比較邏輯 —— 先姓後名
    @Override public int compareTo(Name n) {
        int lastCmp = lastName.compareTo(n.lastName);
        return (lastCmp != 0) ? lastCmp : firstName.compareTo(n.firstName);
    }
}

關鍵設計要點

  1. 不變性:所有字段 final,無 Setter,構造器驗證非空 → 適合作 Set 元素/Map 鍵。
  2. equals/hashCode 一致性:同樣字段參與計算。
  3. compareTo 與 equals 一致:(a.compareTo(b) == 0) == a.equals(b),否則 TreeSet/TreeMap 行為異常。
  4. 比較模板:優先比較最關鍵字段 → 相等則比次關鍵字段 → ... → 返回結果。避免 return a - b 整數溢出陷阱。

Comparator:定制排序邏輯

當需要非自然頩序或類不可修改時,使用 Comparator<T>。

場景:EmployeeRecord 按入職日期倒序 (資歷排序)

public class EmployeeRecord implements Comparable<EmployeeRecord> {
    public Name getName() { ... }
    public int getEmployeeNumber() { ... }
    public Date getHireDate() { ... } // 自然頩序按 name
    // ...
}

// Comparator 實現 (Java 8+ Lambda 風格)
Comparator<EmployeeRecord> SENIORITY_ORDER = (e1, e2) -> {
    int dateCmp = e2.getHireDate().compareTo(e1.getHireDate()); // 倒序
    if (dateCmp != 0) return dateCmp;
    // 同日期按工號升序,保證 Total Order (TreeSet 安全)
    return Integer.compare(e1.getEmployeeNumber(), e2.getEmployeeNumber());
};

Comparator 進階技巧

  • 鏈式組合 (Java 8+):Comparator.comparing(EmployeeRecord::getHireDate).reversed().thenComparingInt(EmployeeRecord::getEmployeeNumber)
  • 空值處理:Comparator.nullsFirst/Last()。
  • 序列化:需實現 Serializable 才能在分布式集合中使用。

排序集合陷阱:Strict Weak Ordering

TreeSet/TreeMap 要求 Comparator 實現全序 (Total Order):compare(a,b)==0 必須等價於 a.equals(b)。
錯誤示例:僅按 hireDate 排序 → 同一天入職員工視為重複 → 第二個無法加入 TreeSet。
修正:添加 tie-breaker (如工號) 確保唯一性,如上例所示。

實戰:多字段排序與反轉

List<EmployeeRecord> emps = ...;
// 1. 按部門升序,部門內按薪資降序,薪資同按姓名升序
emps.sort(Comparator
    .comparing(EmployeeRecord::getDept)
    .thenComparing(EmployeeRecord::getSalary, Comparator.reverseOrder())
    .thenComparing(EmployeeRecord::getName));

// 2. 反轉現有 Comparator
Comparator<EmployeeRecord> byNameDesc = Comparator
    .comparing(EmployeeRecord::getName)
    .reversed();

總結:選擇指南

場景推薦方案
類本身有明顯「自然頩序」且極少變化實現 Comparable<T>
需要多種排序方式 / 第三方類 / 條件排序提供一個或多個 Comparator<T> (靜態常量或 Lambda)
用於 TreeSet/TreeMap務必保證 compare==0 <=> equals,必要時加 tie-breaker
Java 8+ 環境優先用 Comparator.comparing/thenComparing 鏈式 API,可讀性最強

掌握 Comparable 與 Comparator 的契約與最佳實踐,是高效使用 Java 集合框架、避免生產環境排序異常的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved