Stream.sorted: примеры (JAVA)

Сортировка потоков Java методом sorted
Раздел: Потоки данных (Stream API) - промежуточные операции
Stream.sorted: Stream

Описание метода Stream.sorted

Метод Stream.sorted в Java предоставляет способ упорядочивания элементов потока. Это промежуточная, состоящая операцией, которая принимает элементы, буферизует их, выполняет сортировку и возвращает новый поток с установленным порядком элементов.

Сигнатуры и варианты:

  • Stream<T> sorted() - сортировка по естественному порядку. Элементы должны реализовывать Comparable<T>. В противном случае при попытке сравнения возникнет ClassCastException.
  • Stream<T> sorted(Comparator<? super T> comparator) - сортировка с заданным компаратором. Если в качестве аргумента передан null, будет NullPointerException.
  • Примитивные специализированные потоки: IntStream.sorted(), LongStream.sorted(), DoubleStream.sorted() - сортировка примитивных значений по возрастанию.

Поведение и характеристики:

  • Операция является stateful (сохраняет весь вход для сортировки) и «между промежуточными»: сортировка не выполняется лениво для каждого элемента, а только при применении терминальной операции.
  • Для упорядоченных потоков сортировка стабильна - равные элементы сохраняют порядок встречи.
  • Сложность временная примерно O(n log n), требуется дополнительная память O(n) для буферизации всех элементов.
  • В параллельных потоках сортировка выполняется с использованием параллельных алгоритмов, что повышает требования к памяти и синхронизации. Компараторы должны быть потокобезопасны, если они используют внешнее состояние.
  • Возвращаемое значение: новый Stream<T> (или соответствующий примитивный поток) с отсортированными элементами; исходный источник не изменяется.

Когда используется

Когда требуется получить элементы в определенном порядке перед дальнейшей обработкой или коллекционированием - например, при вывода топ-N, агрегировании по упорядоченным критериям, или при представлении данных пользователю.

Короткие примеры использования

1) Сортировка по естественному порядку строк

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream.of("b", "a", "c")
              .sorted()
              .forEach(System.out::print);
    }
}
abc

2) Сортировка с компаратором - обратный порядок

import java.util.stream.Stream;
import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        Stream.of(1, 3, 2)
              .sorted(Comparator.reverseOrder())
              .forEach(System.out::print);
    }
}
321

3) Сортировка примитивного потока

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        IntStream.of(5, 2, 9)
                 .sorted()
                 .forEach(System.out::print);
    }
}
259

4) Сортировка объектов по полю

import java.util.stream.Stream;
import java.util.Comparator;

record Person(String name, int age) {}

public class Main {
    public static void main(String[] args) {
        Stream.of(new Person("Ann", 30), new Person("Bob", 25))
              .sorted(Comparator.comparing(Person::age))
              .forEach(p -> System.out.print(p.name()));
    }
}
BobAnn

Похожие механизмы сортировки в Java

  • Collections.sort(List)
  • Сортирует список на месте. Эффективнее по памяти при необходимости изменить существующий список. Возвращает void и изменяет переданную коллекцию.

  • List.sort(Comparator)
  • Метод интерфейса List, похож на Collections.sort, удобнее при наличии ссылки на список.

  • Arrays.sort / Arrays.parallelSort
  • Сортировка массивов. parallelSort применяет параллельный алгоритм, полезен для больших массивов.

  • TreeSet / TreeMap
  • Структуры данных, которые поддерживают отсортированный набор или карту. Поддерживают динамическое добавление с поддержанием порядка, но обход в них стоит дороже по вставке (O(log n)).

  • PriorityQueue
  • Хороша для извлечения минимального/максимального элемента итеративно. Для получения полного упорядочения потребуется извлечение всех элементов.

Когда предпочесть:

  • Для in-place сортировки коллекции - Collections.sort или List.sort.
  • Для потокового конвейера без изменения исходных данных - Stream.sorted.
  • Для постоянной отсортированной структуры с частыми вставками - TreeSet/TreeMap.

Аналоги в других языках и особенности

Короткие примеры и отличия от поведения Java Stream.sorted.

JavaScript

// Array.prototype.sort
const a = ["b","a","c"];
a.sort();
console.log(a);
[ 'a', 'b', 'c' ]

В JS sort изменяет массив на месте. По умолчанию сортирует как строки; для чисел нужен компаратор.

Python

# sorted возвращает новый список, list.sort меняет на месте
print(sorted([3,1,2]))
[1, 2, 3]

sorted возвращает новый объект, сравнимо с Stream.sorted по смыслу, но API отличается.

SQL

SELECT name FROM users ORDER BY age DESC;
-- Набор строк, упорядоченных по возрасту

ORDER BY выполняет сортировку на уровне базы данных, эффективен при индексах.

C# (LINQ)

var result = new[] {3,1,2}.OrderBy(x => x);
Console.WriteLine(string.Join(',', result));
1,2,3

OrderBy возвращает ленивую последовательность; есть OrderByDescending и ThenBy.

PHP

$a = [3,1,2];
sort($a);
print_r($a);
Array ( [0] => 1 [1] => 2 [2] => 3 )

sort изменяет массив на месте. Для пользовательских сравнений - usort.

Go

package main
import (
    "fmt"
    "sort"
)
func main(){
    a := []int{3,1,2}
    sort.Ints(a)
    fmt.Println(a)
}
[1 2 3]

sort.Ints меняет срез на месте; для пользовательских структур - sort.Slice.

Kotlin

val a = listOf(3,1,2)
println(a.sorted())
[1, 2, 3]

sorted возвращает новый список, sortedWith принимает компаратор.

Lua

a = {3,1,2}
table.sort(a)
for i,v in ipairs(a) do print(v) end
1
2
3

table.sort меняет массив на месте.

Типичные ошибки при использовании Stream.sorted

  • Отсутствует Comparable - при вызове sorted() на объектах без реализации Comparable возникает ClassCastException.
  • import java.util.stream.Stream;
    
    record Item(String id) {}
    
    public class Main {
        public static void main(String[] args) {
            Stream.of(new Item("a"), new Item("b"))
                  .sorted() // Item не Comparable
                  .forEach(System.out::println);
        }
    }
    Exception in thread "main" java.lang.ClassCastException: Main$Item cannot be cast to java.lang.Comparable
  • NullPointerException при передаче null-компаратора
  • Stream.of(1,2).sorted(null).count();
    Exception in thread "main" java.lang.NullPointerException
  • Ожидание изменения исходной коллекции - Stream.sorted не меняет исходный список:
  • List list = new ArrayList<>(List.of(3,1,2));
    list.stream().sorted().collect(Collectors.toList());
    System.out.println(list);
    [3, 1, 2]
  • Непотокобезопасный компаратор в parallel stream - если компаратор использует изменяемое внешнее состояние, в параллельной сортировке появится непредсказуемое поведение.
  • Память и производительность - сортировка большого потока приведет к большой буферизации; для топ-N лучше использовать специализированные подходы.

Изменения и история

Метод Stream.sorted был введен в Java 8 вместе со Stream API. С тех пор сигнатуры не менялись. Примитивные специализированные сортировки (IntStream.sorted и т.д.) также появились в Java 8. В последующих релизах JDK производились внутренние оптимизации реализации потоковых операций и алгоритмов сортировки, но публичный API остался стабильным. Рекомендация - проверять улучшения производительности новых JDK при работе с большими параллельными потоками.

Расширенные и редкие сценарии использования

1) Множественные ключи и стабильность

Пример java
import java.util.stream.Stream;
import java.util.Comparator;

record Person(String name, int age) {}

public class Main {
    public static void main(String[] args) {
        Stream.of(new Person("Bob", 30), new Person("Ann", 30), new Person("Bob", 25))
              .sorted(Comparator.comparing(Person::name).thenComparing(Person::age))
              .forEach(p -> System.out.println(p.name() + ":" + p.age()));
    }
}
Ann:30
Bob:25
Bob:30

thenComparing применяется для вторичного порядка; сортировка сохраняет порядок для равных ключей.

2) Топ-N через sorted + limit

Пример java
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        IntStream.of(5,1,9,3,7)
                 .boxed()
                 .sorted(Comparator.reverseOrder())
                 .limit(3)
                 .forEach(System.out::print);
    }
}
975

Недостаток: сначала сортируются все элементы. Для больших данных можно использовать структуру PriorityQueue для поддержания N лучших элементов без полной сортировки.

3) Топ-N с PriorityQueue - экономия памяти

Пример java
import java.util.*;
import java.util.stream.IntStream;

public class Main {
    public static List topN(IntStream stream, int n){
        PriorityQueue pq = new PriorityQueue<>();
        stream.forEach(x -> {
            if(pq.size() < n) pq.add(x);
            else if (x > pq.peek()){
                pq.poll();
                pq.add(x);
            }
        });
        List res = new ArrayList<>(pq);
        res.sort(Comparator.reverseOrder());
        return res;
    }

    public static void main(String[] args){
        System.out.println(topN(IntStream.of(5,1,9,3,7), 3));
    }
}
[9, 7, 5]

4) Локализованная сортировка строк с Collator

Пример java
import java.text.Collator;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List names = List.of("Łukasz", "Lukas", "Łukasz");
        Collator coll = Collator.getInstance(new Locale("pl"));
        names.stream()
             .sorted(coll)
             .forEach(System.out::println);
    }
}
Lukas
Łukasz
Łukasz

5) Сортировка Map по значениям

Пример java
import java.util.*;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args){
        Map m = Map.of("a",3, "b",1, "c",2);
        var sorted = m.entrySet().stream()
                      .sorted(Map.Entry.comparingByValue())
                      .collect(Collectors.toList());
        System.out.println(sorted);
    }
}
[b=1, c=2, a=3]

6) Сортировка в параллельном потоке - предупреждение

Пример java
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        IntStream.range(0, 1_000_000).parallel()
                 .boxed()
                 .sorted() // возможна высокая нагрузка памяти
                 .count();
    }
}
1000000

Для очень больших наборов стоит оценить потребление памяти и рассмотреть внешнюю сортировку или использование баз данных.

7) Сортировка со сравнением по вычисляемому ключу без создания промежуточных объектов

Пример java
import java.util.stream.Stream;
import java.util.Comparator;

record Item(String id, int score) {}

public class Main {
    public static void main(String[] args) {
        Stream.of(new Item("a",10), new Item("b",5))
              .sorted(Comparator.comparingInt(Item::score))
              .forEach(i -> System.out.print(i.id()));
    }
}
ba

comparingInt/comparingLong/comparingDouble избегают лишнего автобоксинга и эффективнее для примитивных ключей.

джава Stream.sorted function comments

En
Stream.sorted Returns a stream consisting of the elements, sorted according to natural order