跳至主要內容

04. Java比较器和可比较器

LiuSongLing大约 3 分钟javajavalist

Java中的比较很容易,但是当使用自定义类型或者尝试比较不直接可比的对象时,我们需要使用比较策略。

方法有两个,使用 Comparator 或 Comparable 接口。

1.比较

假设有一些球员:

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

我们试着对其进行排序:

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>)

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

public class Player implements Comparable<Player> {

    // same as before

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

}

排序顺序由 compareTo() 方法的返回值决定。如果 x 小于 y,则 Integer.compare(x, y) 返回 -1,如果它们相等,则返回 0,否则返回 1。 该方法返回一个数字,指示被比较的对象是小于、等于还是大于作为参数传递的对象。

2.比较器

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

创建一个 Comparator 来使用 Player 的 ranking 属性对玩家进行排序:

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());
    }

}

这样就能根据不同的需要,使用不同的排序策略:

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

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


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

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

3.java8比较器

Java 8 通过使用 lambda 表达式和 comparing() 静态工厂方法提供了定义 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);

4.优缺点

Comparable 接口是定义默认比较策略的不错选择,换句话说,它是比较对象的主要方式。

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

原因如下:

  • 有时我们无法修改我们想要排序的对象所在的类的源代码,因此无法使用 Comparable
  • 使用 Comparators 可以避免向类添加额外的代码
  • 我们可以定义多个不同的比较策略,这在使用 Comparable 时是不可能的

5.注意

我们一般用 Integer.compare() 方法来比较两个整数。

但有的人会说,为什么不用 Comparator 方法呢,不是挺方面的么:

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

原因就在于它隐含着潜在的风险:

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,这显然是不正确的。

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