實現compareTo方法的指南

1.概述

作為Java開發人員,我們經常需要對集合在一起的元素進行排序。 Java允許我們對任何類型的數據實現各種排序算法

例如,我們可以按字母順序,反向字母順序或基於長度對字符串排序。

在本教程中,我們將探討Comparable接口及其compareTo方法,該方法可以進行排序。我們將研究包含核心和自定義類中的對象的排序集合。

我們還將提及正確實現compareTo規則,以及需要避免的損壞模式。

2.Comparable接口

Comparable接口對實現它的每個類的對象強加排序

compareToComparable接口定義的唯一方法。它通常被稱為自然比較法。

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一致性

讓我們看看當compareToequals實現不一致時會發生什麼。

在我們的示例中, 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.sortArrays.sort對實現Comparable所有對象進行Arrays.sort

3.1。核心Java類

大多數核心Java類(例如StringIntegerDouble )已經實現了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。 TreeMapTreeSet

TreeMapTreeSet是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接口的用例。