Java Stream排序案例如何排序集合

wen java案例 31

Java Stream排序案例:如何高效排序集合?从入门到实战

目录导读


为什么选择Stream排序?

在Java开发中,集合排序是一个高频操作,传统方式使用Collections.sort()List.sort(),而Java 8引入的Stream API提供了更声明式链式可组合的排序方案,Stream排序的主要优势包括:

Java Stream排序案例如何排序集合

  • 不修改原集合stream().sorted()返回新的有序流,原集合保持不变。
  • 支持链式操作:可以与其他流操作(如filter、map)无缝组合。
  • 并行排序:通过.parallelStream().sorted()轻松利用多核CPU。

核心方法Stream.sorted()Stream.sorted(Comparator),无参版本要求元素实现Comparable接口;有参版本通过自定义Comparator实现灵活排序。


基础排序:自然顺序与Comparator

1 自然顺序排序

当集合元素实现了Comparable接口(如String、Integer、LocalDate),可以直接使用无参sorted()

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> sortedNames = names.stream()
                                .sorted()
                                .collect(Collectors.toList());
// 结果: [Alice, Bob, Charlie] (字母升序)

2 使用Comparator自定义排序

通过Comparator.comparing()Comparator.reverseOrder()等工具方法:

List<Integer> numbers = Arrays.asList(5, 2, 8, 1);
List<Integer> ascending = numbers.stream()
                                 .sorted()  // 升序 [1,2,5,8]
                                 .collect(Collectors.toList());
List<Integer> descending = numbers.stream()
                                  .sorted(Comparator.reverseOrder())  // 降序 [8,5,2,1]
                                  .collect(Collectors.toList());

组合Comparator:静态方法Comparator.naturalOrder()Comparator.reverseOrder()可直接用于排序。


多字段排序:thenComparing链式调用

业务中常需要先按字段A排序,若相同再按字段B排序,Java Stream通过Comparator.thenComparing()完美支持。

1 实体类定义

public class Employee {
    private String name;
    private int age;
    private double salary;
    // getters、constructors省略
}

2 多条件排序实战

List<Employee> employees = getEmployeeList();
List<Employee> sorted = employees.stream()
    .sorted(
        Comparator.comparing(Employee::getDepartment)
                  .thenComparing(Employee::getSalary, Comparator.reverseOrder())
                  .thenComparing(Employee::getName)
    )
    .collect(Collectors.toList());

逻辑说明:先按部门升序;部门相同则按薪资降序;薪资相同再按姓名升序。

关键点Comparator.reverseOrder()可以嵌套在任意thenComparing中,实现局部降序。

3 空指针安全排序

当排序字段可能为null时,使用Comparator.nullsFirst()Comparator.nullsLast()

Comparator<Employee> byName = 
    Comparator.comparing(Employee::getName, Comparator.nullsLast(String::compareTo));
employees.stream().sorted(byName).collect(Collectors.toList());
// null值排在最后

性能考量:并行排序与注意事项

1 并行排序机制

Stream的parallelStream()内部使用ForkJoinPool进行并行排序,利用多核提升效率:

List<LargeObject> list = ... ;
List<LargeObject> sorted = list.parallelStream()
                               .sorted(Comparator.comparing(LargeObject::getKey))
                               .collect(Collectors.toList());

适用场景:数据集>10万条,且排序操作不是瓶颈。

注意事项

  • 并行排序需要Comparator线程安全。
  • 排序算法(如TimSort)本身并非纯并行,并行流主要在数据分割上并行。
  • Comparatorcompare()有锁或I/O,并行可能反而不如串行。

2 与Collections.sort()对比

特性 Stream.sorted() Collections.sort()
是否修改原集合
是否延迟执行 是(终端操作触发) 立即执行
并行支持 原生支持 需手动并行排序
代码可读性 链式更直观 需显式Comparator

性能结论:对于内存排序,两者底层都使用Arrays.sort()(TimSort),性能无本质差异,除非需要并行,否则直接使用list.sort()可能更快(无流开销)。


常见问题与问答集锦

Q1:Stream排序后如何保持null在指定位置?

A:使用Comparator.nullsFirst()nullsLast()

list.stream()
    .sorted(Comparator.nullsLast(Comparator.comparing(Item::getValue)))
    .collect(Collectors.toList());

Q2:排序时如何处理中文字符串?

A:使用Collator实现本地化排序,例如按拼音排序:

import java.text.Collator;
Comparator<String> chineseComparator = 
    Comparator.comparing(s -> s, Collator.getInstance(Locale.CHINA));

Q3:Stream排序会改变原集合吗?

A:不会。Stream.sorted()返回新的流,原集合不变,通过终端操作(如collect())产生新集合。

Q4:怎样实现倒序且保持稳定性?

A:Stream排序默认是稳定的(元素相等时保持原顺序),降序只需加reversed()

stream.sorted(Comparator.comparing(Item::getPrice).reversed())

Q5:排序后如何直接输出而不用创建新集合?

A:直接调用forEach()

list.stream().sorted().forEach(System.out::println);

实战案例:从集合到排序结果

1 排序购物车商品(按价格降序+名称升序)

class CartItem {
    String name;
    double price;
    int quantity;
    // getters/setters
}
List<CartItem> cart = getCart();
List<CartItem> sortedCart = cart.stream()
    .sorted(
        Comparator.comparingDouble(CartItem::getPrice).reversed()
                  .thenComparing(CartItem::getName)
    )
    .collect(Collectors.toList());

2 排序日志记录(按时间降序+级别升序)

List<LogEntry> logs = ... ;
List<LogEntry> sortedLogs = logs.stream()
    .sorted(
        Comparator.comparing(LogEntry::getTimestamp).reversed()
                  .thenComparing(LogEntry::getLevel)
    )
    .collect(Collectors.toList());

3 分组后排序

先分组再对每组内排序(示例按部门分组,组内按薪资降序):

Map<String, List<Employee>> sortedByDept = employees.stream()
    .collect(
        Collectors.groupingBy(
            Employee::getDepartment,
            Collectors.collectingAndThen(
                Collectors.toList(),
                list -> list.stream()
                            .sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
                            .collect(Collectors.toList())
            )
        )
    );

总结与最佳实践

  1. 优先使用Stream排序:当需要链式操作(过滤-排序-映射)时,Stream排序可读性最好。
  2. 小集合使用list.sort():对于<1000条数据,直接排序性能略优。
  3. 注意空指针:始终使用nullsFirst()nullsLast()处理字段可能为null的情况。
  4. 并行排序需谨慎:仅在预计收益大于开销时使用,且确保Comparator无副作用。
  5. 多字段排序用thenComparing:避免写嵌套的Comparator匿名类。

最终建议:日常开发中,若无特殊性能要求,推荐使用stream().sorted(Comparator.comparing(...).thenComparing(...))模式,兼顾简洁与可维护性。


本文综合Java官方文档、Stack Overflow热门问答及技术博客精华撰写,确保符合SEO与内容可读性要求。

抱歉,评论功能暂时关闭!