1. 引言

Java中的对象比较看似简单,但实际操作时却容易踩坑。当处理自定义类型或比较非直接可比较的对象时,我们需要定义明确的比较策略。这时就需要借助ComparatorComparable接口来实现。

2. 示例场景搭建

我们以足球队球员排序为例,需要按球员排名进行排列。首先创建简单的Player类:

public class Player {
    private int ranking;
    private String name;
    private int age;
    
    // 构造方法、getter和setter省略
}

接着创建PlayerSorter类来尝试排序:

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("排序前: " + footballTeam);
    Collections.sort(footballTeam);
    System.out.println("排序后: " + footballTeam);
}

不出意外,这段代码会直接报编译错误:

类型 Collections 中的方法 sort(List<T>) 
  对于参数 (ArrayList<Player>) 不适用

接下来我们分析问题根源。

3. Comparable接口

Comparable接口定义了对象与同类对象比较的策略,被称为类的"自然排序"。要让Player对象可排序,需要实现该接口:

public class Player implements Comparable<Player> {

    // 其他代码保持不变

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

✅ 排序规则由compareTo()方法的返回值决定:

  • 返回负数:当前对象小于参数对象
  • 返回零:两者相等
  • 返回正数:当前对象大于参数对象

现在运行PlayerSorter,球员将按排名排序:

排序前: [John, Roger, Steven]
排序后: [Steven, John, Roger]

理解了Comparable的自然排序后,我们来看看如何实现更灵活的排序方式。

4. Comparator接口

Comparator接口定义了compare(arg1, arg2)方法,工作方式与Comparable.compareTo()类似。

4.1 创建Comparator

创建按排名排序的Comparator

public class PlayerRankingComparator implements Comparator<Player> {

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

再创建按年龄排序的Comparator

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

运行结果:

排序前: [John, Roger, Steven]
按排名排序后: [Steven, John, Roger]

切换排序策略只需更换Comparator

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

按年龄排序结果:

排序前: [John, Roger, Steven]
按年龄排序后: [Roger, John, Steven]

4.3 Java 8的Comparator

Java 8提供了更简洁的Comparator创建方式:

使用Lambda表达式:

Comparator<Player> byRanking = 
  (p1, p2) -> Integer.compare(p1.getRanking(), p2.getRanking());

使用Comparator.comparing()静态方法:

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

5. Comparator vs Comparable

Comparable适合定义默认排序规则,但Comparator在以下场景更有优势:

无法修改源码时:当需要排序的类来自第三方库,无法实现Comparable
保持领域类纯净:避免在业务类中添加排序代码
多排序策略支持:同一对象可定义多种比较方式,而Comparable只能有一种

6. 避免减法陷阱

前文我们使用Integer.compare()比较整数,但有人可能想用这种"简洁"写法:

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

理论上"Roger"(-1)应排在"John"(Integer.MAX_VALUE)前面,但实际计算Integer.MAX_VALUE - (-1)会溢出为负数,导致排序结果错误:

assertEquals("John", players.get(0).getName()); // 错误!
assertEquals("Roger", players.get(1).getName());

7. 总结

本文深入探讨了ComparableComparator接口的核心差异:

  • Comparable:定义自然排序,直接在类中实现
  • Comparator:外部排序策略,更灵活多变

要掌握更高级的排序技巧,建议阅读:

完整代码示例可在GitHub仓库获取。


原始标题:Comparator and Comparable in Java | Baeldung