1. 引言
Java中的对象比较看似简单,但实际操作时却容易踩坑。当处理自定义类型或比较非直接可比较的对象时,我们需要定义明确的比较策略。这时就需要借助Comparator
或Comparable
接口来实现。
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. 总结
本文深入探讨了Comparable
和Comparator
接口的核心差异:
Comparable
:定义自然排序,直接在类中实现Comparator
:外部排序策略,更灵活多变
要掌握更高级的排序技巧,建议阅读:
完整代码示例可在GitHub仓库获取。