| 類別 | 自然排序規則 |
| BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short | 數值大小 |
| Character | Unicode 數值大小 |
| String | 字元 Unicode 值逐字比較 (字典序) |
| Date, LocalDate, LocalDateTime | 時間先後順序 |
實作範例: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 行為異常 (如無法正確移除元素)
總結:如何選擇?
- 類別有明確「自然順序」: 實作
Comparable (如數值、日期、字串、ID 排序)
- 需要多種排序方式: 撰寫多個
Comparator 實作類別
- 無法修改原類別 (第三方庫): 唯有使用
Comparator
- Java 8+ 專案: 優先使用
Comparator.comparing... 鏈式 API,簡潔且可讀性高
- 效能敏感場景: 避免在比較器中建立大量臨時物件 (如
toUpperCase()),可預先正規化資料
- 上一篇文章: Java JDBC 實作:將圖片檔案寫入 Oracle BLOB 欄位完整範例
- 下一篇文章: Hibernate outer-join 與 fetch join 深度解析:關聯查詢優化完全指南
Copyright ©
程式碼之家 All Rights Reserved