章
目
录
学习如何使用Comparable接口(默认排序顺序)和Comparator接口(额外的自定义排序顺序)在Java中按字段值对对象列表进行排序。
// 快速指南
List list = ...;
Comparator comparator = Comparator.reverseOrder(); //Create custom order as needed
//1 - List.sort()
list.sort(null);
list.sort(comparator);
//2 - Collections.sort()
Collections.sort(list);
Collections.sort(list, comparator);
//3 - Stream.sorted()
List sortedList = list.stream().sorted().toList(); //or
List reverseList = list.stream().sorted(comparator).toList();
请注意,如果我们一次需要排序数百万条记录,那么数据库查询是最佳方式。否则,使用Comparable或Comparator接口是一种非常便捷的方法。
1.准备
在本教程中提供的示例中,我们将使用名为User的记录类型。它有四个字段:id、firstName、lastName和age。我特意选择了这些字段以展示不同的用例。
//Employee.java
import java.io.Serializable;
public record User(Long id, String firstName, String lastName, Integer age)
implements Serializable {
public User {
if (age < 18) {
throw new IllegalArgumentException("You cannot hire a minor person");
}
}
}
我们将使用提供的未排序列表,并根据不同的字段值对其进行排序。
private static List<User> getUnsortedUsers() {
return Arrays.asList(
new User(1L, "A", "Q", Integer.valueOf(24)),
new User(4L, "B", "P", Integer.valueOf(22)),
new User(2L, "C", "O", Integer.valueOf(27)),
new User(3L, "D", "N", Integer.valueOf(29)),
new User(5L, "E", "M", Integer.valueOf(25)));
}
接下来,我们将使用Comparable和Comparator接口根据不同的字段值进行排序。
2. 使用Comparable进行自然排序的列表排序
2.1. 实现Comparable接口
Comparable接口提供一个方法compareTo(T o),可以由任何类实现,以便比较该类的两个对象。此方法用于实现自然排序行为。
实现Comparable接口后的User记录如下所示。类类型也可以进行类似的实现。默认排序是根据id字段完成的。
public record User(Long id, String firstName, String lastName, Integer age)
implements Serializable, Comparable<User> {
public User {
if (age < 18) {
throw new IllegalArgumentException("You cannot hire a minor person");
}
}
@Override
public int compareTo(User o) {
return this.id.intValue() - o.id.intValue();
}
}
2.2. List.sort() 方法
该方法根据指定比较器引起的顺序对当前列表进行排序。重要的是,该列表中的所有元素必须可以使用指定的比较器进行相互比较。此方法会就地修改列表。
list.sort(Comparator.naturalOrder());
如果指定的比较器为null,那么列表中的所有元素必须实现Comparable接口,然后将使用元素的自然排序顺序。
2.3. Collections.sort() 方法
我们可以将对象列表传递给sort()方法,该方法将按其自然排序顺序对对象进行排序,即按id字段排序。
Collections.sort( list );
查看控制台:
Output[User[id=1, firstName=A, lastName=Q, age=24],
User[id=2, firstName=C, lastName=O, age=27],
User[id=3, firstName=D, lastName=N, age=29],
User[id=4, firstName=B, lastName=P, age=22],
User[id=5, firstName=E, lastName=M, age=25]]
2.4. Stream.sorted() 方法
Java Stream API具有sorted()方法,可以按自然顺序对项目流进行排序。请注意,流操作不会修改原始集合,因此列表中的对象将保持不变。
List<User> sortedList = list.stream()
.sorted()
.collect(Collectors.toList());
3.使用Comparator进行自定义排序的列表排序
3.1. 创建Comparator实例
假设我们想要根据一些其他字段对用户列表进行排序,例如,根据firstName或age。我们可以修改User记录,因为它已经通过id字段实现了自然排序。
这时Comparator接口派上了用场。Comparator可以用于定义自定义排序。要根据不同的对象字段进行排序,我们可以创建多个Comparator实现。
例如,要按firstName对用户列表进行排序,我们可以创建一个实现Comparator的FirstNameSorter类。
//FirstNameSorter.java
import java.util.Comparator;
public class FirstNameSorter implements Comparator<User> {
@Override
public int compare(User o1, User o2) {
return o1.firstName().compareTo(o2.firstName());
}
}
请注意,我们可以使用lambda表达式来创建内联的Comparator实例,用于单次使用。
//使用lambda表达式创建Comparator实例
Comparator<User> firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName());
我们可以通过使用Comparator.thenComparing()方法来组合多个比较器以创建分组排序效果。例如,我们可以创建一个复杂的比较器fullNameSorter,用于按名字和姓氏排序列表。
//复杂的 Comparator
Comparator<User> firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName());
Comparator<User> lastNameSorter = (o1, o2) -> o1.lastName().compareTo(o2.lastName());
Comparator<User> fullNameSorter = firstNameSorter.thenComparing(lastNameSorter);
还值得讨论的另一种Comparator类型是用于逆序排序的。我们可以通过在任何比较器实例上调用reversed()方法来获取逆序比较器。
//逆排序
Comparator<User> reverseSorter = firstNameSorter.reversed();
同样,我们可以在应用程序中创建任意数量的比较器。
3.2. Collections.sort()
要使用Collection.sort()方法进行排序,传递两个方法参数。第一个参数是未排序的列表,第二个参数是Comparator实例。
List<User> list = getUnsortedUsers();
Comparator<User> firstNameSorter
= (o1, o2) -> o1.firstName().compareTo(o2.firstName());
Collections.sort(list, firstNameSorter);
3.3. Stream.sorted()
要使用比较器实例对流中的项进行排序,可以将比较器作为方法参数传递给sorted()方法。
List<User> list = getUnsortedUsers();
Comparator<User> firstNameSorter
= (o1, o2) -> o1.firstName().compareTo(o2.firstName());
List<User> sortedList = list.stream()
.sorted(firstNameSorter)
.collect(Collectors.toList());
4.遵守hashCode()和equals()约束
如果我们在User类中重写了equals()方法,请务必遵守hashCode()和equals()方法之间的约束。
如果两个对象使用equals()方法相等,那么compareTo()方法应返回零。
作为一般的做法,始终在两种方法中使用相同的字段。如果在equals()方法中使用了id字段,那么在compareTo()方法中也应该使用id字段。以下是一个示例实现:
import java.io.Serializable;
import java.util.Objects;
public record User(Long id, String firstName, String lastName, Integer age)
implements Serializable, Comparable<User> {
public User {
if (age < 18) {
throw new IllegalArgumentException("You cannot hire a minor person");
}
}
@Override
public int compareTo(User o) {
return this.id.intValue() - o.id.intValue();
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
return Objects.equals(id, other.id);
}
}
5.结论
在这个Java Comparable和Comparator教程中,我们学习了如何为不同的用例以不同的方式实现这两个接口。我们还看到了在Java Stream API中如何使用这两个接口。
最后,我们了解了如何正确地重写对象的hashCode()和equals()方法,以保持排序功能正常运行。