拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 实现compareTo方法的指南

实现compareTo方法的指南

白鹭 - 2021-11-24 398 0 0

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

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *