Java List排序:Comparable和Comparator示例

培训教学 潘老师 7个月前 (09-28) 174 ℃ (0) 扫码查看

学习如何使用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()方法,以保持排序功能正常运行。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/teach/9337.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】