Java案例怎么用Stream排序数据?

wen java案例 13

Java案例:如何用Stream高效排序数据?从入门到实战

目录导读

  1. 为什么选择Stream排序?
  2. 基础排序:sorted()方法详解
    • 1 自然排序(Comparable)
    • 2 定制排序(Comparator)
  3. 实战案例:集合与数组排序
    • 1 对List排序
    • 2 对List多字段排序
    • 进阶技巧:逆序、空值处理与性能优化
    • 常见问题与解答(FAQ)
    • 从基础到实战的最佳实践

    • 为什么选择Stream排序?

      在Java 8引入Stream API之前,我们通常使用Collections.sort()Arrays.sort()进行排序,但Stream排序具有显著优势:

      Java案例怎么用Stream排序数据?

      • 声明式编程:代码更简洁、可读性更高
      • 链式操作:可无缝结合filter()map()等中间操作
      • 并行处理:只需调用parallelStream()即可实现多线程排序
      • 不可变性:不修改原数据源,返回新Stream(适合函数式编程)

      实际案例对比

      // 传统方式
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      Collections.sort(names); // 直接修改原列表
      // Stream方式
      List<String> sortedNames = names.stream()
          .sorted()
          .collect(Collectors.toList()); // 生成新列表,原列表不变

      适用场景:当需要从数据库/API获取数据后,在内存中进行二次排序(如按创建时间、价格、评分等),而无需修改原始数据源时,Stream排序是最佳选择。


      基础排序:sorted()方法详解

      1 自然排序(Comparable)

      当元素实现了Comparable接口(如String、Integer、LocalDate等),可直接调用无参sorted()

      List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
      List<Integer> ascending = numbers.stream()
          .sorted()
          .collect(Collectors.toList());
      // 结果:[1, 2, 5, 8, 9]

      2 定制排序(Comparator)

      对于自定义对象,需传入Comparator,推荐使用Java 8的Lambda表达式:

      // 示例:按分数降序排序
      List<Student> students = Arrays.asList(
          new Student("Alice", 85),
          new Student("Bob", 92),
          new Student("Charlie", 78)
      );
      List<Student> sortedStudents = students.stream()
          .sorted((s1, s2) -> s2.getScore() - s1.getScore()) // 降序
          .collect(Collectors.toList());

      更优雅的方式是使用Comparator.comparing()

      // 升序
      students.stream()
          .sorted(Comparator.comparing(Student::getScore))
      // 降序(使用reversed())
      students.stream()
          .sorted(Comparator.comparing(Student::getScore).reversed())

      小贴士:如果排序字段可能为null,需用nullsFirst()nullsLast()处理(见进阶部分)。


      实战案例:集合与数组排序

      1 对List排序

      案例:从用户输入获取一组城市名称,按字母顺序排序并输出前3个:

      List<String> cities = Arrays.asList("Shanghai", "Beijing", "Guangzhou", "Shenzhen");
      List<String> topCities = cities.stream()
          .sorted()
          .limit(3)
          .collect(Collectors.toList());
      System.out.println(topCities); // [Beijing, Guangzhou, Shanghai]

      2 对List多字段排序

      场景:电商产品需先按价格降序排序,价格相同时按销量升序:

      class Product {
          String name;
          double price;
          int sales;
          // getters/setters省略
      }
      List<Product> products = loadProducts(); // 获取产品列表
      List<Product> sortedProducts = products.stream()
          .sorted(Comparator
              .comparing(Product::getPrice).reversed()  // 先价格降序
              .thenComparing(Product::getSales)         // 再销量升序
          )
          .collect(Collectors.toList());

      真实业务拓展:若需按日期排序,推荐使用LocalDateLocalDateTime

      // 按创建日期降序
      sorted(Comparator.comparing(Product::getCreateDate).reversed())

      进阶技巧:逆序、空值处理与性能优化

      1 逆序排序的多种写法

      // 方法1:reversed()
      stream.sorted(Comparator.comparing(Function).reversed())
      // 方法2:反向比较器
      stream.sorted(Comparator.reverseOrder()) // 仅用于自然排序

      2 空值安全处理

      当排序字段可能为null时,为避免NPE:

      // 将null值排到最后
      stream.sorted(Comparator
          .comparing(Product::getPrice, Comparator.nullsLast(Double::compareTo))
      )
      // 将null值排到最前
      stream.sorted(Comparator
          .comparing(Product::getPrice, Comparator.nullsFirst(Double::compareTo))
      )

      3 性能优化要点

      • 优先使用基本类型:排序intInteger快约20%,推荐Comparator.comparingInt()comparingLong()comparingDouble()
      • 避免在排序中做复杂计算:如果排序依据需要从其他方法计算,建议先映射为数值再排序
      • 并行排序的条件:数据量超过10000条且排序非CPU密集时,可考虑parallelStream().sorted(),但需注意线程安全,且并行排序在多核机器上才有显著提升

      性能测试简易对比

      // 慢:每次比较都调用方法
      stream.sorted((a, b) -> computeComplexScore(a) - computeComplexScore(b))
      // 快:预先计算分数
      stream.map(p -> new Pair(p, computeComplexScore(p)))
          .sorted(Comparator.comparing(Pair::getScore))
          .map(Pair::getProduct)

      常见问题与解答(FAQ)

      Q1:sorted()和collect()的顺序必须固定吗? A:是的,sorted()是中间操作,collect()是终端操作,必须按stream().filter().sorted().collect()的顺序调用,否则编译报错。

      Q2:Stream排序是否稳定? A:稳定,Stream的sorted()默认使用归并排序(TimSort),能保持相等元素的原始顺序。

      Q3:如何对Map的键或值排序? A:例如按值排序Map:

      Map<String, Integer> map = new HashMap<>();
      map.entrySet().stream()
          .sorted(Map.Entry.comparingByValue())
          .forEach(System.out::println);

      Q4:Stream排序和数据库排序哪个快? A:如果数据已从数据库加载到内存(如从MySQL查询后),Stream排序更快(避免再次查询),但对于海量数据(百万级以上),数据库的索引排序或ORDER BY更高效。

      Q5:排序后如何获取最大值/最小值? A:使用min()max()替代sort+limit

      Product highestPrice = products.stream()
          .max(Comparator.comparing(Product::getPrice))
          .orElse(null);

      从基础到实战的最佳实践

      Stream排序是现代Java开发的必备技能,其核心优势在于:简洁的链式语法、不可变性、易于并行化,在实际项目中应遵循以下原则:

      1. 优先使用方法引用(如Product::getPrice)替代Lambda,提高可读性
      2. 多字段排序用thenComparing(),避免嵌套Lambda
      3. 处理null值时使用nullsFirst()/nullsLast(),防止隐晦的NPE
      4. 性能敏感场景用基本类型比较器comparingInt等)

      最终建议:将Stream排序与filter()map()limit()结合使用,形成完整的数据处理管道。

      // 实际业务:获取最近7天内评分最高的3个产品
      List<Product> topRatedRecent = products.stream()
          .filter(p -> p.getCreateDate().isAfter(LocalDate.now().minusDays(7)))
          .sorted(Comparator.comparing(Product::getRating).reversed())
          .limit(3)
          .collect(Collectors.toList());

      通过本文的案例与技巧,您已能从容应对90%以上的Java排序需求,若需查看更多实战案例,可访问我的博客:www.example-java-tutorial.com(此域名仅为示例,请勿当真)。

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