Java Stream 的 sorted实现自定义排序从基础到高级技巧

 更新时间:2025年08月13日 08:55:05   作者:Java  
本文将介绍如何使用sorted()方法实现自定义排序,涵盖各种常见场景和高级技巧,通过掌握 sorted() 方法的各种用法,你可以灵活应对各种复杂的排序需求,编写出简洁、高效且易于维护的代码,需要的朋友跟随小编一起学习吧

Java Stream API 中的 sorted() 方法是一个强大的中间操作,它允许我们对流中的元素进行排序。默认情况下,sorted() 要求元素实现 Comparable 接口,但在实际应用中,我们经常需要根据特定业务规则进行自定义排序。本文将深入探讨如何使用 sorted() 方法实现自定义排序,涵盖各种常见场景和高级技巧。

一、sorted 方法基础

Java Stream 提供了两种 sorted() 方法重载:

自然排序:要求元素实现 Comparable 接口

Stream<T> sorted()

自定义排序:通过 Comparator 指定排序规则

Stream<T> sorted(Comparator<? super T> comparator)

二、自定义排序的基本实现

1. 使用 Lambda 表达式创建 Comparator

// 示例1:按字符串长度排序
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
List<String> sortedByLength = words.stream()
        .sorted((s1, s2) -> s1.length() - s2.length())
        .toList();
System.out.println(sortedByLength); // 输出:[date, apple, cherry, banana]
// 示例2:按绝对值大小排序
List<Integer> numbers = Arrays.asList(-5, 2, -8, 1, 3);
List<Integer> sortedByAbs = numbers.stream()
        .sorted((n1, n2) -> Math.abs(n1) - Math.abs(n2))
        .toList();
System.out.println(sortedByAbs); // 输出:[1, 2, 3, -5, -8]

2. 使用 Comparator 静态方法简化代码

Java 8 为 Comparator 接口提供了许多实用的静态方法,使排序代码更加简洁:

// 使用 Comparator.comparing 方法
List<String> sortedByLength2 = words.stream()
        .sorted(Comparator.comparing(String::length))
        .toList();
// 使用 Comparator.comparingInt 优化基本类型比较
List<Integer> sortedByAbs2 = numbers.stream()
        .sorted(Comparator.comparingInt(Math::abs))
        .toList();

三、处理复杂对象排序

1. 对自定义对象按属性排序

class Person {
    private String name;
    private int age;
    private LocalDate birthDate;
    // 构造方法、getter和setter略
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", birthDate=" + birthDate + "}";
    }
}
// 按年龄升序排序
List<Person> people = Arrays.asList(
    new Person("Alice", 25, LocalDate.of(2000, 1, 1)),
    new Person("Bob", 20, LocalDate.of(2005, 5, 5)),
    new Person("Charlie", 30, LocalDate.of(1995, 10, 10))
);
List<Person> sortedByAge = people.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();
System.out.println(sortedByAge);
// 输出:[Person{name='Bob', age=20}, Person{name='Alice', age=25}, Person{name='Charlie', age=30}]

2. 多条件排序(复合排序)

使用 thenComparing() 方法可以实现多级排序:

// 先按年龄升序,年龄相同则按出生日期降序
List<Person> sortedByAgeAndBirthDate = people.stream()
        .sorted(Comparator.comparingInt(Person::getAge)
                .thenComparing(Person::getBirthDate, Comparator.reverseOrder()))
        .toList();
System.out.println(sortedByAgeAndBirthDate);

四、处理空值与 null 安全排序

1. 空值处理策略

在实际应用中,集合元素或元素属性可能为 null,直接排序会导致 NullPointerException。我们可以使用 Comparator.nullsFirst() 或 Comparator.nullsLast() 来安全处理 null 值:

// 示例:处理可能为 null 的字符串
List<String> stringsWithNulls = Arrays.asList("apple", null, "banana", null, "cherry");
// null 值排在前面
List<String> sortedWithNullsFirst = stringsWithNulls.stream()
        .sorted(Comparator.nullsFirst(Comparator.naturalOrder()))
        .toList();
System.out.println(sortedWithNullsFirst); 
// 输出:[null, null, apple, banana, cherry]
// null 值排在后面
List<String> sortedWithNullsLast = stringsWithNulls.stream()
        .sorted(Comparator.nullsLast(Comparator.naturalOrder()))
        .toList();
System.out.println(sortedWithNullsLast); 
// 输出:[apple, banana, cherry, null, null]

2. 对象属性可能为 null 的情况

class Product {
    private String name;
    private Double price; // 价格可能为 null
    // 构造方法、getter和setter略
}
List<Product> products = Arrays.asList(
    new Product("Laptop", 1200.0),
    new Product("Mouse", null),
    new Product("Keyboard", 50.0)
);
// 按价格排序,null 价格排在最后
List<Product> sortedByPrice = products.stream()
        .sorted(Comparator.comparing(Product::getPrice, Comparator.nullsLast(Double::compare)))
        .toList();

五、逆序排序与自定义比较逻辑

1. 逆序排序

使用 Comparator.reverseOrder() 或 Comparator.comparing().reversed() 实现逆序:

// 字符串长度逆序排序
List<String> reversedByLength = words.stream()
        .sorted(Comparator.comparing(String::length).reversed())
        .toList();
// 另一种逆序写法
List<String> reversedByLength2 = words.stream()
        .sorted((s1, s2) -> s2.length() - s1.length())
        .toList();

2. 自定义复杂比较逻辑

对于更复杂的业务规则,可以实现 Comparator 接口:

// 示例:按字符串长度排序,长度相同则按字母顺序排序
List<String> complexSort = words.stream()
        .sorted(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                int lengthCompare = Integer.compare(s1.length(), s2.length());
                if (lengthCompare != 0) {
                    return lengthCompare;
                }
                return s1.compareTo(s2);
            }
        })
        .toList();
// 使用 Lambda 简化
List<String> complexSort2 = words.stream()
        .sorted((s1, s2) -> {
            int lengthCompare = Integer.compare(s1.length(), s2.length());
            return lengthCompare != 0 ? lengthCompare : s1.compareTo(s2);
        })
        .toList();

六、性能优化与注意事项

1. 基本类型与装箱类型

对于基本类型(如 intlongdouble),优先使用 comparingIntcomparingLongcomparingDouble 避免装箱拆箱开销:

// 性能优化示例
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 避免装箱
List<Integer> optimizedSort = numbers.stream()
        .sorted(Comparator.comparingInt(Integer::intValue))
        .toList();

2. 排序稳定性

Stream.sorted() 使用的是稳定排序算法(TimSort),即相等元素的相对顺序不会改变。这在多级排序中尤为重要:

// 示例:先按部门排序,再按工资排序
List<Employee> employees = ...;
List<Employee> sortedEmployees = employees.stream()
        .sorted(Comparator.comparing(Employee::getDepartment)
                .thenComparingDouble(Employee::getSalary))
        .toList();

3. 并行流排序性能

在并行流中,排序操作可能会导致性能下降,因为需要全局数据重组。谨慎在并行流中使用复杂排序:

// 并行流排序示例
List<Integer> parallelSorted = numbers.parallelStream()
        .sorted()
        .toList();

七、实战案例

1. 电商商品排序系统

class Product {
    private String name;
    private double price;
    private int salesVolume;
    private LocalDateTime createTime;
    // 构造方法、getter和setter略
}
// 按价格升序排序
List<Product> sortedByPrice = products.stream()
        .sorted(Comparator.comparingDouble(Product::getPrice))
        .toList();
// 按销量降序,销量相同则按创建时间降序
List<Product> sortedBySalesAndTime = products.stream()
        .sorted(Comparator.comparingInt(Product::getSalesVolume).reversed()
                .thenComparing(Product::getCreateTime, Comparator.reverseOrder()))
        .toList();

2. 日志时间戳排序

class LogEntry {
    private LocalDateTime timestamp;
    private String message;
    private LogLevel level;
    // 构造方法、getter和setter略
}
// 按时间戳排序
List<LogEntry> sortedLogs = logs.stream()
        .sorted(Comparator.comparing(LogEntry::getTimestamp))
        .toList();
// 按日志级别排序(自定义顺序:ERROR > WARN > INFO > DEBUG)
List<LogEntry> sortedByLevel = logs.stream()
        .sorted(Comparator.comparing(LogEntry::getLevel, 
                Comparator.comparingInt(level -> {
                    switch (level) {
                        case ERROR: return 4;
                        case WARN: return 3;
                        case INFO: return 2;
                        case DEBUG: return 1;
                        default: return 0;
                    }
                }).reversed()))
        .toList();

八、总结与最佳实践

优先使用方法引用和静态工具方法

// 推荐写法
sorted(Comparator.comparing(Person::getAge))
// 避免冗余的 Lambda
sorted((p1, p2) -> p1.getAge() - p2.getAge())

多级排序使用链式调用

sorted(Comparator.comparing(Person::getDepartment)
        .thenComparing(Person::getAge)
        .thenComparing(Person::getName))

处理 null 值

sorted(Comparator.nullsLast(Comparator.comparing(Person::getName)))

基本类型优化

sorted(Comparator.comparingInt(Person::getAge)) // 避免装箱

复杂比较器提取为常量

public static final Comparator<Person> AGE_NAME_COMPARATOR = 
    Comparator.comparingInt(Person::getAge)
            .thenComparing(Person::getName);
// 使用时
sorted(AGE_NAME_COMPARATOR)

通过掌握 sorted() 方法的各种用法,你可以灵活应对各种复杂的排序需求,编写出简洁、高效且易于维护的代码。在实际开发中,合理运用 Comparator 的各种工具方法和特性,能够显著提升代码质量和开发效率。

到此这篇关于java多线程的文章就介绍到这了,更多相关java多线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java正则表达式之全量匹配和部分匹配

    Java正则表达式之全量匹配和部分匹配

    正则表达式异常强大,一直理解不深,用的也不深,这次项目中尝试,体会到了它的强大之处,这篇文章主要给大家介绍了关于Java正则表达式之全量匹配和部分匹配的相关资料,需要的朋友可以参考下
    2023-06-06
  • java实现二分法查找出数组重复数字

    java实现二分法查找出数组重复数字

    这篇文章主要为大家详细介绍了java实现二分法查找出数组重复数字,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-11-11
  • SpringBoot中swagger的使用

    SpringBoot中swagger的使用

    这篇文章主要介绍了SpringBoot中swagger的使用,文中有非常详细的代码示例,对正在学习swagger的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-05-05
  • Java正则提取中括号中的内容操作示例

    Java正则提取中括号中的内容操作示例

    这篇文章主要介绍了Java正则提取中括号中的内容操作,涉及Java针对字符串的正则匹配、转换、遍历等相关操作技巧,需要的朋友可以参考下
    2018-06-06
  • springmvc请求转发和重定向问题(携带参数和不携带参数)

    springmvc请求转发和重定向问题(携带参数和不携带参数)

    这篇文章主要介绍了springmvc请求转发和重定向问题(携带参数和不携带参数),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Java ThreadLocal用法实例详解

    Java ThreadLocal用法实例详解

    这篇文章主要介绍了Java ThreadLocal用法,结合实例形式详细分析了ThreadLocal线程局部变量相关原理、定义与使用方法,需要的朋友可以参考下
    2019-09-09
  • 详解Spring AOP的原理与实现方式

    详解Spring AOP的原理与实现方式

    Spring框架是一个功能强大且灵活的企业级应用程序开发框架,其中最重要的特性之一就是面向切面编程(AOP),我们今天这篇文章将从源码和案例的角度详细介绍Spring AOP的思想、原理和实现方式
    2023-07-07
  • 流式图表拒绝增删改查之kafka核心消费逻辑下篇

    流式图表拒绝增删改查之kafka核心消费逻辑下篇

    这篇文章主要为大家介绍了流式图表拒绝增删改查之kafka核心消费逻辑讲解的下篇,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • JSONObject按put顺序排放与输出方式

    JSONObject按put顺序排放与输出方式

    这篇文章主要介绍了JSONObject按put顺序排放与输出方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 解决IDEA中不能正常输入光标变粗的问题

    解决IDEA中不能正常输入光标变粗的问题

    这篇文章主要介绍了在IDEA中不能正常输入光标变粗的解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-09-09

最新评论