Comparator and Comparable in Java – Java中的比较器和可比性

最后修改: 2017年 12月 1日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Comparisons in Java are quite easy, until they’re not.

在Java中进行比较是很容易的,直到它们不那么容易。

When working with custom types, or trying to compare objects that aren’t directly comparable, we need to make use of a comparison strategy. We can build one simply by making use of the Comparator or Comparable interfaces.

当使用自定义类型时,或者试图比较那些不能直接比较的对象时,我们需要使用一个比较策略。我们可以通过使用ComparatorComparable接口简单地建立一个。

2. Setting Up the Example

2.设置实例

Let’s use an example of a football team, where we want to line up the players by their rankings.

让我们用一个足球队的例子来说明,我们想按照球员的排名来排队。

We’ll start by creating a simple Player class:

我们将从创建一个简单的Player类开始。

public class Player {
    private int ranking;
    private String name;
    private int age;
    
    // constructor, getters, setters  
}

Next, we’ll create a PlayerSorter class to create our collection, and attempt to sort it using Collections.sort:

接下来,我们将创建一个PlayerSorter类来创建我们的集合,并尝试使用Collections.sort对它进行排序。

public static void main(String[] args) {
    List<Player> footballTeam = new ArrayList<>();
    Player player1 = new Player(59, "John", 20);
    Player player2 = new Player(67, "Roger", 22);
    Player player3 = new Player(45, "Steven", 24);
    footballTeam.add(player1);
    footballTeam.add(player2);
    footballTeam.add(player3);

    System.out.println("Before Sorting : " + footballTeam);
    Collections.sort(footballTeam);
    System.out.println("After Sorting : " + footballTeam);
}

As expected, this results in a compile-time error:

正如预期的那样,这导致了一个编译时错误。

The method sort(List<T>) in the type Collections 
  is not applicable for the arguments (ArrayList<Player>)

Now let’s try to understand what we did wrong here.

现在让我们试着理解我们在这里做错了什么。

3. Comparable

3.可比性

As the name suggests, Comparable is an interface defining a strategy of comparing an object with other objects of the same type. This is called the class’s “natural ordering.”

顾名思义,Comparable是一个接口,定义了一个对象与其他同类型对象的比较策略。这被称为该类的 “自然排序”

In order to be able to sort, we must define our Player object as comparable by implementing the Comparable interface:

为了能够进行排序,我们必须通过实现Comparable接口将我们的Player对象定义为可比较的。

public class Player implements Comparable<Player> {

    // same as before

    @Override
    public int compareTo(Player otherPlayer) {
        return Integer.compare(getRanking(), otherPlayer.getRanking());
    }

}

The sorting order is decided by the return value of the compareTo() method. The Integer.compare(x, y) returns -1 if is less than y, 0 if they’re equal, and 1 otherwise.

排序顺序由compareTo()方法的返回值决定。Integer.compare(x, y)如果x小于y,则返回-1,如果它们相等,则返回0,否则返回1。

The method returns a number indicating whether the object being compared is less than, equal to, or greater than the object being passed as an argument.

该方法返回一个数字,表明被比较的对象是否小于、等于或大于作为参数传递的对象。

Now when we run our PlayerSorter, we can see our Players sorted by their ranking:

现在,当我们运行我们的PlayerSorter时,我们可以看到我们的Players按其排名排序。

Before Sorting : [John, Roger, Steven]
After Sorting : [Steven, John, Roger]

Now that we have a clear understanding of natural ordering with Comparable, let’s see how we can use other types of ordering in a more flexible manner than by directly implementing an interface.

现在我们对使用Comparable的自然排序有了清晰的认识,让我们看看我们如何以更灵活的方式使用其他类型的排序,而不是直接实现一个接口。

4. Comparator

4.比较器

The Comparator interface defines a compare(arg1, arg2) method with two arguments that represent compared objects, and works similarly to the Comparable.compareTo() method.

比较器接口定义了一个compare(arg1, arg2) 方法,它的两个参数代表了被比较的对象,并且与Comparable.compareTo()方法类似。

4.1. Creating Comparators

4.1.创建比较器

To create a Comparator, we have to implement the Comparator interface.

为了创建一个比较器,我们必须实现比较器接口。

For our first example, we’ll create a Comparator to use the ranking attribute of Player to sort the players:

对于我们的第一个例子,我们将创建一个Comparator,使用Playerranking属性来对球员进行排序。

public class PlayerRankingComparator implements Comparator<Player> {

    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return Integer.compare(firstPlayer.getRanking(), secondPlayer.getRanking());
    }

}

Similarly, we can create a Comparator to use the age attribute of Player to sort the players:

同样,我们可以创建一个比较器来使用球员年龄属性来对球员进行排序。

public class PlayerAgeComparator implements Comparator<Player> {

    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return Integer.compare(firstPlayer.getAge(), secondPlayer.getAge());
    }

}

4.2. Comparators in Action

4.2.比较器的作用

To demonstrate the concept, let’s modify our PlayerSorter by introducing a second argument to the Collections.sort method, which is actually the instance of Comparator we want to use.

为了演示这个概念,让我们修改我们的PlayerSorter,为Collections.sortmethod引入第二个参数,这实际上是我们想要使用的Comparator的实例。

Using this approach, we can override the natural ordering:

使用这种方法,我们可以推翻自然排序

PlayerRankingComparator playerComparator = new PlayerRankingComparator();
Collections.sort(footballTeam, playerComparator);

Now let’s run our PlayerRankingSorter to see the result:

现在让我们运行我们的PlayerRankingSorter来看看结果。

Before Sorting : [John, Roger, Steven]
After Sorting by ranking : [Steven, John, Roger]

If we want a different sorting order, we only need to change the Comparator we’re using:

如果我们想要一个不同的排序顺序,我们只需要改变我们正在使用的Comparator

PlayerAgeComparator playerComparator = new PlayerAgeComparator();
Collections.sort(footballTeam, playerComparator);

Now when we run our PlayerAgeSorter, we can see a different sort order by age:

现在,当我们运行我们的PlayerAgeSorter时,我们可以看到一个不同的排序顺序,按年龄:

Before Sorting : [John, Roger, Steven]
After Sorting by age : [Roger, John, Steven]

4.3. Java 8 Comparators

4.3. Java 8比较器

Java 8 provides new ways of defining Comparators by using lambda expressions, and the comparing() static factory method.

Java 8通过使用lambda表达式和comparing()静态工厂方法提供了定义Comparators的新方法。

Let’s see a quick example of how to use a lambda expression to create a Comparator:

让我们看一个快速的例子,如何使用lambda表达式来创建一个Comparator

Comparator byRanking = 
  (Player player1, Player player2) -> Integer.compare(player1.getRanking(), player2.getRanking());

The Comparator.comparing method takes a method calculating the property that will be used for comparing items, and returns a matching Comparator instance:

Comparator.comparing方法接收一个计算将用于比较项目的属性的方法,并返回一个匹配的Comparator实例。

Comparator<Player> byRanking = Comparator
  .comparing(Player::getRanking);
Comparator<Player> byAge = Comparator
  .comparing(Player::getAge);

To explore the Java 8 functionality in-depth, check out our Java 8 Comparator.comparing guide.

要深入探索 Java 8 的功能,请查看我们的Java 8 Comparator.comparing指南。

5. Comparator vs Comparable

5.ComparatorComparable

The Comparable interface is a good choice to use for defining the default ordering, or in other words, if it’s the main way of comparing objects.

Comparable接口是用于定义默认排序的好选择,或者换句话说,如果它是比较对象的主要方式。

So why use a Comparator if we already have Comparable?

那么,如果我们已经有了Comparable,为什么还要使用一个Comparator

There are several reasons why:

有几个原因。

  • Sometimes we can’t modify the source code of the class whose objects we want to sort, thus making the use of Comparable impossible
  • Using Comparators allows us to avoid adding additional code to our domain classes
  • We can define multiple different comparison strategies, which isn’t possible when using Comparable

6. Avoiding the Subtraction Trick

6.避免 “减法 “把戏

Over the course of this tutorial, we’ve used the Integer.compare() method to compare two integers. However, one might argue that we should use this clever one-liner instead:

在本教程中,我们已经使用Integer.compare()方法来比较两个整数。然而,有人可能会说,我们应该用这个聪明的单行线来代替。

Comparator<Player> comparator = (p1, p2) -> p1.getRanking() - p2.getRanking();

Although it’s much more concise than other solutions, it can be a victim of integer overflows in Java:

尽管它比其他解决方案要简洁得多,但它可能是Java中整数溢出的受害者

Player player1 = new Player(59, "John", Integer.MAX_VALUE);
Player player2 = new Player(67, "Roger", -1);

List<Player> players = Arrays.asList(player1, player2);
players.sort(comparator);

Since -1 is much less than the Integer.MAX_VALUE, “Roger” should come before “John” in the sorted collection. However, due to integer overflow, the “Integer.MAX_VALUE – (-1)” will be less than zero. So based on the Comparator/Comparable contract, the Integer.MAX_VALUE is less than -1, which is obviously incorrect.

由于-1远远小于Integer.MAX_VALUE,在排序的集合中,”Roger “应该排在 “John “之前。然而,由于整数溢出,“Integer.MAX_VALUE – (-1)”将小于零。所以根据Comparator/Comparablecontract,Integer.MAX_VALUE小于-1,这显然是不正确的。

Therefore, despite what we expected, “John” comes before “Roger” in the sorted collection:

因此,尽管我们预料到了,但在排序的集合中,”约翰 “排在 “罗杰 “之前。

assertEquals("John", players.get(0).getName());
assertEquals("Roger", players.get(1).getName());

7. Conclusion

7.结论

In this article, we explored the Comparable and Comparator interfaces, and discussed the differences between them.

在这篇文章中,我们探讨了ComparableComparator接口,并讨论了它们之间的区别。

To understand more advanced topics of sorting, check out our other articles, such as Java 8 Comparator, and Java 8 Comparison with Lambdas.

要了解排序的更多高级主题,请查看我们的其他文章,如Java 8比较器,以及Java 8与Lambdas比较

As usual, the source code can be found over on GitHub.

像往常一样,源代码可以在GitHub上找到over