Java中的Comparator和Comparable

评论 0 浏览 0 2017-12-02

1.绪论

在 Java 中进行比较非常容易,除非它们不是。

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

2.设置实例

让我们举一个足球队的例子,我们想按排名排列球员。

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

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

接下来,我们将创建一个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);
}

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

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

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

3.Comparable

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

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

public class Player implements Comparable<Player> {

    // same as before

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

}

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

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

现在,当我们运行我们的PlayerSorter时,我们可以看到我们的Players按照他们的排名进行排序:

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

现在我们已经清楚地了解了使用Comparable的自然排序,让我们看看我们如何以更灵活的方式使用其他类型的排序,而不是直接实现一个接口。

4.Comparator

Comparator 接口定义了一个compare(arg1, arg2) 方法,它有两个参数,代表被比较的对象,其工作方式类似于Comparable.compareTo() 方法。

4.1.创建Comparator

要创建一个 Comparator,我们必须实现 Comparator 接口。

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

public class PlayerRankingComparator implements Comparator<Player> {

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

}

同样地,我们可以创建一个 Comparator 来使用Player age 属性来对球员进行排序:

public class PlayerAgeComparator implements Comparator<Player> {

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

}

4.2.Comparator 的使用

为了证明这个概念,让我们修改我们的PlayerSorter,为Collections.sort 方法引入第二个参数,这实际上是我们要使用的Comparator的实例。

使用这种方法,我们可以覆盖自然的排序

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

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

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

如果我们想要一个不同的排序顺序,我们只需要改变我们所使用的Comparator即可:

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

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

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

4.3. Java 8 比较器

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

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

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

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

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

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

5.ComparatorComparable对比

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

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

这有几个原因:

  • 有时我们不能修改我们想要排序的对象的类的源代码,从而使得Comparable的使用变得不可能
  • 使用Comparator可以让我们避免在领域类中添加额外的代码
  • 我们可以定义多个不同的比较策略,这在使用Comparable的时候是不可能的。

6.避免减法技巧

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

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

尽管它比其他解决方案要简洁得多,但它可能是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);

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

因此,尽管我们预料到了,但在排序的集合中,“John”排在了“Roger”之前:

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

7.结语

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

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

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

最后更新2023-04-03
0 个评论
标签