1. Overview
1.概述
As Java developers, we often need to sort elements that are grouped together in a collection. Java allows us to implement various sorting algorithms with any type of data.
作为Java开发者,我们经常需要对集合中的元素进行排序。Java允许我们用任何类型的数据实现各种排序算法。
For example, we can sort strings in alphabetical order, reverse alphabetical order, or based on length.
例如,我们可以按字母顺序、反向字母顺序或基于长度对字符串进行排序。
In this tutorial, we’ll explore the Comparable interface and its compareTo method, which enables sorting. We’ll look at sorting collections that contain objects from both core and custom classes.
在本教程中,我们将探讨Comparable接口及其compareTo方法,它可以实现排序。我们将研究如何对包含核心类和自定义类对象的集合进行排序。
We’ll also mention rules for properly implementing compareTo, as well as a broken pattern that needs to be avoided.
我们还将提到正确实现compareTo的规则,以及一个需要避免的破碎模式。
2. The Comparable Interface
2.可比较的界面
The Comparable interface imposes ordering on the objects of each class that implements it.
Comparable接口对实现它的每个类的对象施加了排序。
The compareTo is the only method defined by the Comparable interface. It is often referred to as the natural comparison method.
compareTo是Comparable接口定义的唯一方法。它通常被称为自然比较法。
2.1. Implementing compareTo
2.1.实现compareTo
The compareTo method compares the current object with the object sent as a parameter.
compareTo方法将当前对象与作为参数发送的对象进行比较。
When implementing it, we need to make sure that the method returns:
在实现它时,我们需要确保该方法的返回。
- A positive integer, if the current object is greater than the parameter object
- A negative integer, if the current object is less than the parameter object
- Zero, if the current object is equal to the parameter object
In mathematics, we call this a sign or a signum function:
在数学中,我们称其为符号或符号函数。
2.2. Example Implementation
2.2.实施实例
Let’s take a look at how the compareTo method is implemented in the core Integer class:
让我们来看看compareTo方法是如何在核心Integer类中实现的。
@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. The Broken Subtraction Pattern
2.3.破碎的减法模式
One might argue that we can use a clever subtraction one-liner instead:
有人可能会说,我们可以用一个巧妙的减法单行线来代替。
@Override
public int compareTo(BankAccount anotherAccount) {
return this.balance - anotherAccount.balance;
}
Let’s consider an example where we expect a positive account balance to be greater than a negative one:
让我们考虑一个例子,我们期望正数的账户余额大于负数。
BankAccount accountOne = new BankAccount(1900000000);
BankAccount accountTwo = new BankAccount(-2000000000);
int comparison = accountOne.compareTo(accountTwo);
assertThat(comparison).isNegative();
However, an integer is not big enough to store the difference, thus giving us the wrong result. Certainly, this pattern is broken due to possible integer overflow and needs to be avoided.
然而,一个整数不够大,无法存储差值,因此给了我们错误的结果。当然,这种模式由于可能的整数溢出而被破坏,需要避免。
The correct solution is to use comparison instead of subtraction. We may also reuse the correct implementation from the core Integer class:
正确的解决方案是使用比较法而不是减法。我们也可以重新使用核心Integer类中的正确实现。
@Override
public int compareTo(BankAccount anotherAccount) {
return Integer.compare(this.balance, anotherAccount.balance);
}
2.4. Implementation Rules
2.4.执行规则
In order to properly implement the compareTo method, we need to respect the following mathematical rules:
为了正确实现compareTo方法,我们需要尊重以下数学规则。
- sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
- (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0
- x.compareTo(y) == 0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z))
It is also strongly recommended, though not required, to keep the compareTo implementation consistent with the equals method implementation:
我们还强烈建议,尽管不是必须的,保持compareTo实现与equals方法的实现:。
- x.compareTo(e2) == 0 should have the same boolean value as x.equals(y)
This will ensure that we can safely use objects in sorted sets and sorted maps.
这将确保我们可以安全地使用排序集和排序图中的对象。
2.5. Consistency with equals
2.5.与等价物的一致性
Let’s take a look at what can happen when the compareTo and equals implementations are not consistent.
让我们看看当compareTo和equals实现不一致时会发生什么。
In our example, the compareTo method is checking goals scored, while the equals method is checking the player name:
在我们的例子中,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);
}
This may result in unexpected behavior when using this class in sorted sets or sorted maps:
当在排序集或排序图中使用该类时,这可能会导致意外的行为。
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);
A sorted set performs all element comparisons using compareTo and not the equals method. Thus, the two players seem equivalent from its perspective, and it will not add the second player.
一个排序的集合使用compareTo而不是equals方法来执行所有的元素比较。因此,从它的角度来看,这两个球员似乎是相等的,它不会添加第二个球员。
3. Sorting Collections
3.对藏品进行分类
The main purpose of the Comparable interface is to enable the natural sorting of elements grouped in collections or arrays.
Comparable接口的主要目的是实现对集合或数组中的元素进行自然排序。
We can sort all objects that implement Comparable using the Java utility methods Collections.sort or Arrays.sort.
我们可以使用Java实用方法Collections.sort或Arrays.sort对所有实现Comparable的对象进行排序。
3.1. Core Java Classes
3.1.核心Java类
Most core Java classes, like String, Integer, or Double, already implement the Comparable interface.
大多数核心Java类,如String、Integer或Double,已经实现了Comparable接口。
Thus, sorting them is very simple since we can reuse their existing, natural sorting implementation.
因此,对它们进行排序是非常简单的,因为我们可以重新使用它们现有的、自然的排序实现。
Sorting numbers in their natural order will result in ascending order:
按照自然顺序对数字进行排序,结果是升序。
int[] numbers = new int[] {5, 3, 9, 11, 1, 7};
Arrays.sort(numbers);
assertThat(numbers).containsExactly(1, 3, 5, 7, 9, 11);
On the other hand, the natural sorting of strings will result in alphabetical order:
另一方面,字符串的自然排序将导致按字母顺序排列。
String[] players = new String[] {"ronaldo", "modric", "ramos", "messi"};
Arrays.sort(players);
assertThat(players).containsExactly("messi", "modric", "ramos", "ronaldo");
3.2. Custom Classes
3.2.自定义类
In contrast, for any custom classes to be sortable, we need to manually implement the Comparable interface.
相反,对于任何自定义类的可排序,我们需要手动实现Comparable 接口。
The Java compiler will throw an error if we try to sort a collection of objects that do not implement Comparable.
如果我们试图对一个没有实现Comparable的对象集合进行排序,Java编译器会抛出一个错误。
If we try the same with arrays, it will not fail during compilation. However, it will result in a class cast runtime exception:
如果我们对数组进行同样的尝试,在编译时不会失败。然而,它将导致一个类的投递运行时异常。
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 and TreeSet
3.3.TreeMap和TreeSet
TreeMap and TreeSet are two implementations from the Java Collections Framework that assist us with the automatic sorting of their elements.
TreeMap和TreeSet是来自Java集合框架的两个实现,帮助我们对其元素进行自动排序。
We may use objects that implement the Comparable interface in a sorted map or as elements in a sorted set.
我们可以在排序地图中使用实现Comparable接口的对象,或者作为排序集合的元素。
Let’s look at an example of a custom class that compares players based on the number of goals they have scored:
让我们看看一个自定义类的例子,它根据球员的进球数来比较他们的情况。
@Override
public int compareTo(FootballPlayer anotherPlayer) {
return Integer.compare(this.goalsScored, anotherPlayer.goalsScored);
}
In our example, keys are automatically sorted based on criteria defined in the compareTo implementation:
在我们的例子中,键是根据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. The Comparator Alternative
4.比较器替代品
Besides natural sorting, Java also allows us to define a specific ordering logic in a flexible way.
除了自然排序,Java还允许我们以灵活的方式定义一个特定的排序逻辑。
The Comparator interface allows for multiple different comparison strategies detached from the objects we are sorting:
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);
It is generally also a good choice when we don’t want to or can’t modify the source code of the objects we want to sort.
一般来说,当我们不想或不能修改我们想要排序的对象的源代码时,这也是一个不错的选择。
5. Conclusion
5.总结
In this article, we looked into how we can use the Comparable interface to define a natural sorting algorithm for our Java classes. We looked at a common broken pattern and defined how to properly implement the compareTo method.
在这篇文章中,我们研究了如何使用Comparable接口为我们的Java类定义一个自然的排序算法。我们研究了一个常见的破碎模式,并定义了如何正确实现compareTo方法。
We also explored sorting collections that contain both core and custom classes. Next, we considered the implementation of the compareTo method in classes used in sorted sets and sorted maps.
我们还探索了包含核心类和自定义类的排序集合。接下来,我们考虑了在排序集合和排序地图中使用的类的compareTo方法的实现。
Finally, we looked at a few use-cases when we should make use of the Comparator interface instead.
最后,我们看了一些我们应该使用Comparator接口的用例。
As always, the source code is available over on GitHub.
一如既往,源代码可在GitHub上获取。