Java Stream 的 sorted 方法:自定义排序全解析

目录

一、sorted 方法基础

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

1. 使用 Lambda 表达式创建 Comparator

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

三、处理复杂对象排序

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

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

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

1. 空值处理策略

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

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

1. 逆序排序

2. 自定义复杂比较逻辑

六、性能优化与注意事项

1. 基本类型与装箱类型

2. 排序稳定性

3. 并行流排序性能

七、实战案例

1. 电商商品排序系统

2. 日志时间戳排序

八、总结与最佳实践


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

一、sorted 方法基础

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

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

    Stream<T> sorted()
    
  2. 自定义排序:通过 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();
八、总结与最佳实践
  1. 优先使用方法引用和静态工具方法

    // 推荐写法
    sorted(Comparator.comparing(Person::getAge))
    
    // 避免冗余的 Lambda
    sorted((p1, p2) -> p1.getAge() - p2.getAge())
    
  2. 多级排序使用链式调用

    sorted(Comparator.comparing(Person::getDepartment)
            .thenComparing(Person::getAge)
            .thenComparing(Person::getName))
    
  3. 处理 null 值

    sorted(Comparator.nullsLast(Comparator.comparing(Person::getName)))
    
  4. 基本类型优化

    sorted(Comparator.comparingInt(Person::getAge)) // 避免装箱
    
  5. 复杂比较器提取为常量

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潜意识Java

源码一定要私信我,有问题直接问

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值