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

Java Comparable 與 Comparator 深度解析:自然排序與定製排序完全指南

     編輯:Java 綜合教學

Java Comparable 與 Comparator 深度解析:自然排序與定製排序完全指南

排序是程式設計基礎操作。Java 提供兩大核心介面實現物件比較:Comparable (內部比較、自然排序) 與 Comparator (外部比較、定製排序)。本文深度解析兩者區別、實現規範、常見陷阱與實戰選型指南。

一、Comparable: 自然排序 (內建比較)

介面定義

public interface Comparable<T> {
    int compareTo(T o);  // 返回: 負數(小於)、0(等於)、正數(大於)
}

核心契約 (必須遵守)

  1. 反對稱性:sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  2. 傳遞性:(x.compareTo(y) > 0 && y.compareTo(z) > 0) ⇒ x.compareTo(z) > 0
  3. 與 equals 一致性 (強烈建議):(x.compareTo(y) == 0) == x.equals(y)
    • 違反不會導致運行錯誤,但會違反 SortedSet/SortedMap 協定
    • 例:BigDecimal 的 compareTo 與 equals 不一致 (1.0 vs 1.00)
  4. null 參數拋 NullPointerException (非規定但慣例)

實現範例: Person 類按年齡自然排序

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    private char sex;
    // 構造器、getter/setter、toString 省略

    @Override
    public int compareTo(Person o) {
        // 基本類型直接減法 (注意溢出風險,int 安全)
        return this.age - o.age;  
        // 或包裝類 compareTo: Integer.compare(this.age, o.age)
        // 字串比較: this.name.compareTo(o.name)
    }
}

使用場景

// TreeSet 自動按自然排序去重
TreeSet<Person> set = new TreeSet<>();
set.add(new Person("李四", 34, '男'));
set.add(new Person("張三", 23, '女'));
set.add(new Person("王五", 13, '男'));
// 遍歷輸出: 王五(13) → 張三(23) → 李四(34)

// Arrays/Collections 排序
List<Person> list = Arrays.asList(p1, p2, p3);
Collections.sort(list);  // 依賴 compareTo

二、Comparator: 定製排序 (外部策略)

介面定義 (JDK 8+ 函數式介面)

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    
    // JDK 8 默認方法:鏈式組合
    default Comparator<T> thenComparing(Comparator<? super T> other) { ... }
    default Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor) { ... }
    static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor) { ... }
    static <T> Comparator<T> naturalOrder() { ... }
    static <T> Comparator<T> reverseOrder() { ... }
}

實現範例: 多條件複合排序 (年齡 → 姓名 → 性別)

public class PersonComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        // 1. 年齡升序
        int ageDiff = Integer.compare(o1.getAge(), o2.getAge());
        if (ageDiff != 0) return ageDiff;
        
        // 2. 姓名字典序
        int nameDiff = o1.getName().compareTo(o2.getName());
        if (nameDiff != 0) return nameDiff;
        
        // 3. 性別字符比較
        return Character.compare(o1.getSex(), o2.getSex());
    }
}

// JDK 8+ 鏈式寫法 (更優雅、可讀性強)
Comparator<Person> comparator = Comparator
    .comparing(Person::getAge)
    .thenComparing(Person::getName)
    .thenComparing(Person::getSex);

使用場景

// TreeSet 指定比較器
TreeSet<Person> set = new TreeSet<>(new PersonComparator());
// 或匿名內部類
TreeSet<Person> set2 = new TreeSet<>((o1, o2) -> o1.getAge() - o2.getAge());

// List 排序 (不修改原對象,靈活多變)
list.sort(comparator);
Collections.sort(list, comparator);
Arrays.sort(array, comparator);

三、核心區別對比表

維度ComparableComparator
定義位置類內部 (實現介面)類外部 (獨立類/匿名類/Lambda)
比較邏輯固定、唯一 (自然排序)可變、多種 (定製策略)
修改權限需修改源碼無需修改源碼 (第三方類也可排序)
適用場景單一默認排序規則多種排序需求、無法修改類
TreeSet/TreeMap 構造無參構造器需傳 Comparator 構造器
Collections.sort()單參版本雙參版本
方法簽名compareTo(T o)compare(T o1, T o2)
函數式介面否是 (JDK 8+)

四、進階主題

1. 基本類型比較工具 (避免溢出、簡化代碼)

// 推薦使用 JDK 7+ 比較器工具
Integer.compare(a, b);
Long.compare(a, b);
Double.compare(a, b);  // 處理 NaN 語義
Float.compare(a, b);

// 而非 a - b (可能溢出: Integer.MAX_VALUE - (-1) = 負數 導致邏輯錯誤)

2. 空值處理策略

// JDK 8+ 空值友好比較器
Comparator<String> nullsFirst = Comparator.nullsFirst(Comparator.naturalOrder());
Comparator<String> nullsLast = Comparator.nullsLast(Comparator.naturalOrder());

// 自定義: 空值視為最小
Comparator<Person> safe = Comparator
    .comparing(Person::getName, Comparator.nullsFirst(String::compareTo))
    .thenComparing(Person::getAge);

3. 反轉排序

Comparator<Person> desc = Comparator.comparing(Person::getAge).reversed();
// 或
Collections.sort(list, Collections.reverseOrder(comparator));

4. 比較器一致性陷阱

// 錯誤示範: 違反傳遞性
Comparator<Person> bad = (o1, o2) -> {
    if (o1.getAge() == o2.getAge()) return 0;
    return o1.getName().compareTo(o2.getName());  // 年齡不同卻按名字排,破壞傳遞性
};

// TreeSet 使用壞比較器會導致元素丟失、查找失效、死循環

五、實戰選型指南

  1. 類設計者、單一默認排序 → 實現 Comparable (如 String、Integer、Date、實體類主鍵)
  2. 使用者、多種排序、第三方類 → 提供 Comparator (如按姓名/年齡/薪資排序員工)
  3. TreeSet/TreeMap 鍵類型 → 必須實現 Comparable 或構造時傳 Comparator
  4. JDK 8+ 首選 Lambda/方法引用 + 鏈式 API → 代碼即文檔,類型安全

六、完整可運行示例

import java.util.*;

public class SortDemo {
    static class Person {
        String name; int age; char sex;
        Person(String n, int a, char s) { name=n; age=a; sex=s; }
        public String getName() { return name; }
        public int getAge() { return age; }
        public char getSex() { return sex; }
        public String toString() { return name+"("+age+","+sex+")"; }
    }

    public static void main(String[] args) {
        List<Person> list = Arrays.asList(
            new Person("李四", 34, '男'),
            new Person("張三", 23, '女'),
            new Person("王五", 23, '男'),  // 同年齡測試次序
            new Person("趙六", 13, '女')
        );

        System.out.println("原始: " + list);

        // 1. Comparable 自然排序 (需 Person 實現 Comparable)
        // Collections.sort(list); 

        // 2. Comparator 定製: 年齡升序 → 姓名升序
        list.sort(Comparator.comparing(Person::getAge)
                            .thenComparing(Person::getName));
        System.out.println("年齡→姓名: " + list);

        // 3. 年齡降序 → 姓名升序
        list.sort(Comparator.comparing(Person::getAge).reversed()
                            .thenComparing(Person::getName));
        System.out.println("年齡降→姓名: " + list);

        // 4. TreeSet 自動去重排序
        TreeSet<Person> set = new TreeSet<>(Comparator
            .comparing(Person::getAge)
            .thenComparing(Person::getName)
            .thenComparing(Person::getSex));
        set.addAll(list);
        System.out.println("TreeSet: " + set);
    }
}

核心心法:Comparable 定義「這個物件本質上怎麼排」,Comparator 定義「在這個場景下怎麼排」。前者是物件的內在屬性,後者是外部策略。JDK 8+ Comparator 鏈式 API 讓複雜排序邏輯變得清晰、可組合、易測試,是現代 Java 排序的最佳實踐。

掌握比較器契約 (反對稱、傳遞、與 equals 一致) 並善用 JDK 8+ 工具方法,能讓排序代碼既正確又優雅。

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