ZetCode

Java Stream sorted

最后修改于 2025 年 5 月 24 日

本文深入探讨了 Java Stream 的 sorted 方法,这是一种用于对流中的元素进行排序的多功能工具。 它使开发人员能够以指定的顺序排列数据,例如数字、字符串或自定义对象,使其成为以函数式编程风格处理集合的重要功能。

sorted 方法是 Java Stream API 中的一个中间操作,它生成一个新的流,其中原始流的元素以定义的顺序排列。 作为一个有状态的操作,它必须处理整个流才能确定正确的顺序,这可能会影响大型数据集的性能。 它支持对实现 Comparable 接口的元素进行自然顺序排序,或者按照提供的 Comparator 定义的自定义顺序进行排序。

基本的 sorted 语法

sorted 方法有两个变体

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

第一个变体 sorted 按照元素的自然顺序排列元素,要求这些元素实现 Comparable 接口,例如字符串(按字母顺序排序)或数字(按数值排序)。 如果元素未实现 Comparable,则会发生运行时异常。

第二个变体 sorted(Comparator) 允许使用提供的 Comparator 进行自定义排序逻辑,从而实现灵活的排序,例如反向排序或按特定对象属性排序。 这使其适用于复杂的排序需求。 这两种变体都返回一个新的流,使原始数据源保持不变,并且针对并行处理进行了优化,但开发人员应注意大型流的内存使用情况,因为该操作具有有状态的性质。

数字的自然排序

使用自然顺序对数字进行排序。

Main.java
void main() {

    Stream<Integer> numbers = Stream.of(5, 3, 8, 1, 2, 7, 4, 6);
    
    numbers.sorted()
          .forEach(System.out::println);
}

此示例使用整数的自然顺序以升序对整数进行排序。 没有参数的 sorted 方法依赖于元素实现 Comparable

反向排序

使用 Comparator.reverseOrder 对元素进行反向排序。

Main.java
void main() {

    Stream<String> words = Stream.of("banana", "apple", "pear", "orange");
    
    words.sorted(Comparator.reverseOrder())
         .forEach(System.out::println);
}

此示例以反向字母顺序对字符串进行排序。 Comparator.reverseOrder 方法返回一个比较器,该比较器强制执行自然顺序的反向。

$ java Main.java
pear
orange
banana
apple

自定义对象排序

在下面的示例中,我们将使用 Comparator 对自定义对象进行排序。 这是处理用户定义类型集合时的常见情况,例如按年龄或姓名对人员列表进行排序。

Main.java
record Person(String firstName, String lastName, String occupation) {
}

void main() {

    Stream<Person> people = Stream.of(
        new Person("John", "Doe", "Engineer"),
        new Person("Jane", "Smith", "Doctor"),
        new Person("Alice", "Johnson", "Artist"),
        new Person("Bob", "Brown", "Engineer"),
        new Person("Charlie", "Davis", "Teacher"),
        new Person("Diana", "Wilson", "Artist")

    );
    
    people.sorted(Comparator.comparing(Person::occupation))
          .forEach(p -> System.out.println(p));
}

此示例使用 Comparator 按职业对 Person 对象进行排序。 comparing 方法创建一个比较器,该比较器基于指定的属性比较 Person 对象,在本例中为职业。

$ java Main.java
Person[firstName=Alice, lastName=Johnson, occupation=Artist]
Person[firstName=Diana, lastName=Wilson, occupation=Artist]
Person[firstName=Jane, lastName=Smith, occupation=Doctor]
Person[firstName=John, lastName=Doe, occupation=Engineer]
Person[firstName=Bob, lastName=Brown, occupation=Engineer]
Person[firstName=Charlie, lastName=Davis, occupation=Teacher]

此示例演示如何按出生日期对 Person 对象进行排序。

Main.java
record Person(String firstName, String lastName, String dob) {
    public int age() {

        return Period.between(LocalDate.parse(dob), LocalDate.now()).getYears();
    }
}

void main() {

    Stream<Person> people = Stream.of(

            new Person("John", "Doe", "1987-01-01"),
            new Person("Jane", "Smith", "1990-05-15"),
            new Person("Alice", "Johnson", "2000-03-20"),
            new Person("Bob", "Brown", "1995-07-30"),
            new Person("Charlie", "Davis", "1980-12-10"),
            new Person("Diana", "Wilson", "2002-11-25")
    );

    people.sorted(Comparator.comparing(Person::dob))
            .forEach(p -> System.out.println(p));
}

此示例按出生日期 (dob) 对 Person 对象进行排序。 comparing 方法创建一个比较器,该比较器基于指定的属性比较 Person 对象,在本例中为出生日期。 dob 字段应为 yyyy-MM-dd 格式,以便进行正确的按时间顺序排序。

多个排序标准

在下一个示例中,我们将按多个标准对员工列表进行排序:部门、薪水和姓名。

Main.java
record Employee(String name, String department, double salary) {
}

void main() {

    Stream<Employee> employees = Stream.of(
        new Employee("Alice", "HR", 75000),
        new Employee("Bob", "IT", 80000),
        new Employee("Charlie", "IT", 75000),
        new Employee("Diana", "HR", 80000)
    );
    
    employees.sorted(Comparator.comparing(Employee::department)
                             .thenComparing(Employee::salary)
                             .thenComparing(Employee::name))
            .forEach(e -> System.out.println(
                e.department() + " - " + e.salary() + " - " + e.name()));
}

此示例首先按部门、然后按薪水、最后按姓名对员工进行排序。 thenComparing 方法允许链接多个比较器。

$ java Main.java
HR - 75000.0 - Alice
HR - 80000.0 - DianathenComparing
IT - 75000.0 - Charlie
IT - 80000.0 - Bob

不区分大小写的字符串排序

在下一个示例中,我们将以不区分大小写的方式对字符串进行排序。

Main.java
void main() {

    Stream<String> mixedCase = Stream.of("apple", "Banana", "cherry", "Date");
    
    mixedCase.sorted(String.CASE_INSENSITIVE_ORDER)
             .forEach(System.out::println);
}

此示例对字符串进行排序,忽略大小写差异。 String.CASE_INSENSITIVE_ORDER 比较器提供字符串的不区分大小写的比较。

$ java Main.java
apple
Banana
cherry
Date

使用空值进行排序

下一个示例演示如何使用 Comparator.nullsFirstComparator.nullsLast 处理排序中的空值。

Main.java
void main() {

    Stream<String> withNulls = Stream.of("banana", null, "apple", null, "cherry");
    
    System.out.println("Nulls first:");
    withNulls.sorted(Comparator.nullsFirst(Comparator.naturalOrder()))
             .forEach(System.out::println);
    
    System.out.println("\nNulls last:");
    Stream<String> withNulls2 = Stream.of("banana", null, "apple", null, "cherry");
    withNulls2.sorted(Comparator.nullsLast(Comparator.naturalOrder()))
              .forEach(System.out::println);
}

此示例演示了如何处理排序中的空值。 nullsFirst 将空值放在非空元素之前,而 nullsLast 将它们放在之后。

$ java Main.java
Nulls first:
null
null
apple
banana
cherry

Nulls last:
apple
banana
cherry
null
null

来源

Java Stream sorted 文档

在本文中,我们探讨了 Java Stream 的 sorted 方法。 它提供了灵活的选项,可以使用自然排序或自定义比较器对流元素进行排序。 理解排序对于 Java 中有效的流处理和数据操作至关重要。

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程