實現compareTo方法的指南
1.概述
作為Java開發人員,我們經常需要對集合在一起的元素進行排序。 Java允許我們對任何類型的數據實現各種排序算法。
例如,我們可以按字母順序,反向字母順序或基於長度對字符串排序。
在本教程中,我們將探討Comparable
接口及其compareTo
方法,該方法可以進行排序。我們將研究包含核心和自定義類中的對象的排序集合。
我們還將提及正確實現compareTo
規則,以及需要避免的損壞模式。
2.Comparable接口
Comparable
接口對實現它的每個類的對象強加排序。
compareTo
是Comparable
接口定義的唯一方法。它通常被稱為自然比較法。
2.1。實現compareTo
compareTo
方法將當前對象與作為參數發送的對象進行比較。
在實現它時,我們需要確保該方法返回:
- 如果當前對像大於參數對象,則為正整數
- 一個負整數,如果當前對像小於參數對象
- 如果當前對像等於參數對象,則為零
在數學中,我們稱其為符號或符號函數:
2.2。示例實施
讓我們看一下如何在核心Integer
類中實現compareTo
方法:
@Override
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare (int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
2.3。破碎的減法模式
有人可能會爭辯說我們可以改用聰明的減法一線法:
@Override
public int compareTo(BankAccount anotherAccount) {
return this.balance - anotherAccount.balance;
}
讓我們考慮一個示例,在該示例中,我們希望帳戶的正餘額大於負的餘額:
BankAccount accountOne = new BankAccount(1900000000);
BankAccount accountTwo = new BankAccount(-2000000000);
int comparison = accountOne.compareTo(accountTwo);
assertThat(comparison).isNegative();
但是,整數不足以存儲差值,因此給我們錯誤的結果。當然,此模式由於可能的整數溢出而被破壞,需要避免。
正確的解決方案是使用比較而不是減法。我們還可以重用核心Integer
類中的正確實現:
@Override
public int compareTo(BankAccount anotherAccount) {
return Integer.compare(this.balance, anotherAccount.balance);
}
2.4。實施規則
為了正確實現compareTo
方法,我們需要遵守以下數學規則:
-
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
-
(x.compareTo(y) > 0 && y.compareTo(z) > 0)
意味著x.compareTo(z) > 0
-
x.compareTo(y) == 0
表示sgn(x.compareTo(z)) == sgn(y.compareTo(z))
強烈建議(儘管不是必需的)使compareTo
實現與**equals**
方法實現**保持一致**:
-
x.compareTo(e2) == 0
應該具有與x.equals(y)
相同的布爾值
這將確保我們可以安全地在已排序的集合和已排序的地圖中使用對象。
2.5。 equals
一致性
讓我們看看當compareTo
和equals
實現不一致時會發生什麼。
在我們的示例中, compareTo
方法正在檢查進球數,而equals
方法正在檢查玩家姓名:
@Override
public int compareTo(FootballPlayer anotherPlayer) {
return this.goalsScored - anotherPlayer.goalsScored;
}
@Override
public boolean equals(Object object) {
if (this == object)
return true;
if (object == null || getClass() != object.getClass())
return false;
FootballPlayer player = (FootballPlayer) object;
return name.equals(player.name);
}
在排序集或排序映射中使用此類時,可能會導致意外的行為:
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 800);
TreeSet<FootballPlayer> set = new TreeSet<>();
set.add(messi);
set.add(ronaldo);
assertThat(set).hasSize(1);
assertThat(set).doesNotContain(ronaldo);
排序的集合使用compareTo
而不是equals
方法執行所有元素比較。因此,從其角度看,這兩個玩家似乎是等效的,並且不會添加第二個玩家。
3.排序集合
Comparable
接口的主要目的是使對集合或數組中的元素進行自然排序。
我們可以使用Java實用程序方法Collections.sort
或Arrays.sort
對實現Comparable
所有對象進行Arrays.sort
。
3.1。核心Java類
大多數核心Java類(例如String
, Integer
或Double
)已經實現了Comparable
接口。
因此,對它們進行排序非常簡單,因為我們可以重用它們現有的自然排序實現。
按自然順序對數字進行排序將導致升序:
int[] numbers = new int[] {5, 3, 9, 11, 1, 7};
Arrays.sort(numbers);
assertThat(numbers).containsExactly(1, 3, 5, 7, 9, 11);
另一方面,字符串的自然排序將導致字母順序:
String[] players = new String[] {"ronaldo", "modric", "ramos", "messi"};
Arrays.sort(players);
assertThat(players).containsExactly("messi", "modric", "ramos", "ronaldo");
3.2。自定義類
相反,對於任何可排序的自定義類,我們需要手動實現Comparable
接口。
如果我們嘗試對未實現Comparable
的對象的集合進行排序,則Java編譯器將引發錯誤。
如果我們對數組嘗試相同的操作,則編譯期間不會失敗。但是,這將導致類強制轉換運行時異常:
HandballPlayer duvnjak = new HandballPlayer("Duvnjak", 197);
HandballPlayer hansen = new HandballPlayer("Hansen", 196);
HandballPlayer[] players = new HandballPlayer[] {duvnjak, hansen};
assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> Arrays.sort(players));
3.3。 TreeMap
和TreeSet
TreeMap
和TreeSet
是Java Collections Framework的兩個實現,可幫助我們對它們的元素進行自動排序。
我們可以使用在排序映射中或在排序集中的元素中實現Comparable
接口的對象。
讓我們看一個自定義類的示例,該類根據球員得分的目標比較球員:
@Override
public int compareTo(FootballPlayer anotherPlayer) {
return Integer.compare(this.goalsScored, anotherPlayer.goalsScored);
}
在我們的示例中,鍵是根據compareTo實現中定義的條件自動排序的:
FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 900);
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer modric = new FootballPlayer("modric", 100);
Map<FootballPlayer, String> players = new TreeMap<>();
players.put(ronaldo, "forward");
players.put(messi, "forward");
players.put(modric, "midfielder");
assertThat(players.keySet()).containsExactly(modric, messi, ronaldo);
4. Comparator
替代
除了自然排序外, Java還允許我們以靈活的方式定義特定的排序邏輯。
Comparator
接口允許從我們正在排序的對像中分離出多種不同的比較策略:
FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 900);
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer modric = new FootballPlayer("Modric", 100);
List<FootballPlayer> players = Arrays.asList(ronaldo, messi, modric);
Comparator<FootballPlayer> nameComparator = Comparator.comparing(FootballPlayer::getName);
Collections.sort(players, nameComparator);
assertThat(players).containsExactly(messi, modric, ronaldo);
當我們不想或無法修改要排序的對象的源代碼時,這通常也是一個不錯的選擇。
5.結論
在本文中,我們研究瞭如何使用Comparable
接口為Java類定義自然排序算法。我們研究了一個常見的損壞模式,並定義瞭如何正確實現compareTo
方法。
我們還探討了同時包含核心和自定義類的排序集合。接下來,我們考慮了在排序集和排序映射中使用的類中compareTo
方法的實現。
最後,我們研究了一些應該使用Comparator
接口的用例。