Сортировка в Java делается встроенными методами:
list.sort(comparator) или
Collections.sort(list) для списков,
Arrays.sort(array) для массивов,
stream().sorted() для потоков данных. Как сравнивать элементы, Java узнает из интерфейса
Comparable (естественный порядок, встроенный в класс) или
Comparator (своя логика для конкретного случая). Раньше разработчики писали свои алгоритмы сортировки (пузырьковая, быстрая и так далее), но сейчас это делать не нужно: в Java уже есть готовые, оптимизированные решения со сложностью O(n log n). Давай разберем, как ими пользоваться.
Кратко
- Списки сортируют методом list.sort(comparator) (с Java 8) или Collections.sort(list): оба меняют список на месте, причем второй с Java 8 просто вызывает первый.
- Массивы, включая примитивные, сортирует Arrays.sort(array); исходный массив при этом меняется, для копии сначала Arrays.copyOf().
- Stream API (stream().sorted()) не трогает исходную коллекцию и возвращает новый список.
- Порядок сравнения задает Comparable (метод compareTo() внутри класса, один естественный порядок) или Comparator (отдельный объект, любой порядок; с лямбдами и Comparator.comparing() это одна строка, многоуровневая сортировка через thenComparing()).
- TreeSet и TreeMap держат элементы отсортированными всегда: поиск, вставка и удаление за O(log n).
- Свои алгоритмы сортировки в рабочем коде не нужны: встроенные методы работают за O(n log n) и стабильны, а ручной двойной цикл дает O(n²) и лишние баги.
![Разноцветные деревянные чашки, выстроенные по размеру от большой к маленькой, и россыпь цветных камешков рядом: наглядная аналогия сортировки]()
Основы: Comparable и Comparator
Прежде чем начать сортировать, нужно понять главное: Java должна знать, как сравнивать объекты. Для чисел это очевидно (5 меньше 10), а для твоих собственных классов нет. Тут на помощь приходят два интерфейса:
- Comparable: для естественного порядка сортировки (встраивается в класс)
- Comparator: для кастомной логики сортировки (отдельный объект)
Интерфейс Comparable
Если твой класс имеет "естественный" порядок сортировки (например, студенты по имени), реализуй интерфейс
Comparable:
class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Student other) {
return this.name.compareTo(other.name); // по имени
}
}
Метод
compareTo() должен возвращать:
- Отрицательное число, если текущий объект меньше
- Ноль, если объекты равны
- Положительное число, если текущий объект больше
Интерфейс Comparator
Когда нужна гибкость (например, сортировать то по имени, то по возрасту), используй
Comparator. С Java 8 это стало невероятно удобно благодаря лямбдам:
List<Student> students = Arrays.asList(
new Student("Анна", 22),
new Student("Борис", 20),
new Student("Виктор", 21)
);
// Сортировка по возрасту (лямбда)
students.sort((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
// Еще проще: через method reference
students.sort(Comparator.comparingInt(Student::getAge));
// Результат: Борис(20), Виктор(21), Анна(22)Сортировка коллекций
Метод Collections.sort()
Классический способ: использовать статический метод
Collections.sort(). Он работает со списками (List):
List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9));
Collections.sort(numbers);
System.out.println(numbers);
// Вывод: [1, 2, 5, 8, 9]
Для обратного порядка используй
Collections.reverseOrder():
Collections.sort(numbers, Collections.reverseOrder());
System.out.println(numbers);
// Вывод: [9, 8, 5, 2, 1]Метод List.sort(): современный подход
С Java 8 у списков появился собственный метод
sort(), который удобнее:
List<String> fruits = Arrays.asList("Яблоко", "Банан", "Апельсин");
// Естественный порядок
fruits.sort(null); // или Comparator.naturalOrder()
System.out.println(fruits);
// Вывод: [Апельсин, Банан, Яблоко]
По длине строки:
fruits.sort(Comparator.comparingInt(String::length));
System.out.println(fruits);
// Вывод: [Банан, Яблоко, Апельсин]Сортировка массивов
Для массивов используй
Arrays.sort(), он работает с любыми типами данных:
// Примитивы
int[] numbers = {5, 2, 8, 1, 9};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
// Вывод: [1, 2, 5, 8, 9]
С объектами и Comparator:
String[] words = {"Зебра", "Анаконда", "Бабочка"};
Arrays.sort(words, (a, b) -> Integer.compare(a.length(), b.length()));
System.out.println(Arrays.toString(words));
// Вывод: [Зебра, Бабочка, Анаконда]
Важный момент: Arrays.sort() изменяет исходный массив. Если нужно сохранить оригинал, сделай копию:
int[] original = {5, 2, 8};
int[] sorted = Arrays.copyOf(original, original.length);
Arrays.sort(sorted);
Stream API: функциональный подход
Stream API из Java 8 позволяет сортировать данные в функциональном стиле, не изменяя исходную коллекцию:
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
List<Integer> sorted = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Исходный: " + numbers);
System.out.println("Отсортированный: " + sorted);
// Исходный: [5, 2, 8, 1, 9]
// Отсортированный: [1, 2, 5, 8, 9]
С Java 16 есть вариант короче:
.toList() вместо
.collect(Collectors.toList()). Разница в том, что
toList() возвращает неизменяемый список: добавить в него элемент уже не получится, вызов закончится
UnsupportedOperationException.
С кастомной логикой:
List<String> names = Arrays.asList("Алексей", "Мария", "Иван");
List<String> sortedByLength = names.stream()
.sorted(Comparator.comparingInt(String::length).reversed())
.collect(Collectors.toList());
System.out.println(sortedByLength);
// Вывод: [Алексей, Мария, Иван] (от длинных к коротким)Самосортирующиеся структуры данных
Иногда нужно, чтобы данные всегда были отсортированы. Для этого используй
TreeSet и
TreeMap: они автоматически поддерживают порядок элементов.
TreeSet: отсортированное множество
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(numbers);
// Вывод: [1, 2, 5, 8] (автоматически отсортировано)
С кастомным компаратором (обратный порядок):
Set<String> words = new TreeSet<>(Collections.reverseOrder());
words.add("Яблоко");
words.add("Банан");
words.add("Апельсин");
System.out.println(words);
// Вывод: [Яблоко, Банан, Апельсин] (в обратном алфавитном порядке)TreeMap: отсортированный словарь
TreeMap сортирует пары ключ-значение по ключу:
Map<String, Integer> scores = new TreeMap<>();
scores.put("Алексей", 95);
scores.put("Мария", 87);
scores.put("Иван", 92);
scores.forEach((name, score) ->
System.out.println(name + ": " + score)
);
// Алексей: 95
// Иван: 92
// Мария: 87
Важно: операции поиска, вставки и удаления в TreeSet/TreeMap имеют сложность O(log n), это быстрее линейного поиска по ArrayList или LinkedList. И еще одна деталь: равенство элементов эти структуры проверяют только через
compareTo() или компаратор, а не через
equals(). Если сравнение вернуло 0, второй элемент просто не добавится:
TreeSet с компаратором по длине строки из «Банан» и «Слива» оставит только одно слово.
Практические примеры
Сортировка по нескольким полям
class Employee {
private String department;
private String name;
private int salary;
// конструктор, геттеры
}
List<Employee> employees = // ... заполнение списка
// Сортировка: отдел → зарплата (убывание) → имя
employees.sort(
Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getName)
);
Сортировка Map по значениям
По умолчанию
TreeMap сортирует только по ключам. Чтобы отсортировать по значениям, проще всего пропустить пары через Stream API и сложить результат в
LinkedHashMap, который помнит порядок вставки:
Map<String, Integer> unsortedMap = new HashMap<>();
unsortedMap.put("Анна", 87);
unsortedMap.put("Борис", 95);
unsortedMap.put("Виктор", 82);
Map<String, Integer> sortedByValue = unsortedMap.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
// {Виктор=82, Анна=87, Борис=95}Что НЕ нужно делать
Главное правило: не пиши свои алгоритмы сортировки, если в этом нет крайней необходимости. Вот пример того, что встречается в старом коде, но делать этого не стоит:
// ❌ Плохой подход: ручная сортировка обменом за O(n²)
double[] arr = {5.2, 2.1, 8.4, 1.7};
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[i]) {
double temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
Почему это плохо:
- Низкая производительность: сложность O(n²) против O(n log n) у встроенных методов
- Больше кода: 10 строк против одной
- Больше ошибок: легко допустить баг в индексах
- Плохая читаемость: другим разработчикам нужно время, чтобы понять логику
Правильный вариант занимает одну строку:
// ✅ Правильный подход
Arrays.sort(arr);
Исключение: писать свою сортировку имеет смысл только если ты делаешь что-то очень специфичное (например, сортируешь данные во время их чтения с диска для экономии памяти) или это учебная задача на собеседовании.
Подводим итоги
Вот краткая шпаргалка по сортировке в Java:
- Списки: list.sort(comparator) или Collections.sort(list)
- Массивы: Arrays.sort(array)
- Stream API: stream().sorted().collect()
- Всегда отсортировано: TreeSet, TreeMap
- Кастомная логика: Comparator с лямбдами или method references
- Многоуровневая: comparing().thenComparing()
Чем способы отличаются друг от друга
Все они дают одинаково отсортированный результат, разница в том, что именно сортируется и что происходит с исходными данными:
| Способ |
Что сортирует |
Исходные данные |
Когда выбирать |
| list.sort(), Collections.sort() |
любой изменяемый List |
сортируются на месте |
обычная сортировка списка |
| Arrays.sort() |
массивы примитивов и объектов |
сортируются на месте, копия через Arrays.copyOf() |
массивы, в том числе int[] |
| stream().sorted() |
любой источник данных для Stream |
не меняются, результат в новом списке |
нужна копия или цепочка filter, map, sorted |
| TreeSet, TreeMap |
множество, словарь по ключу |
хранятся отсортированными постоянно |
данные все время меняются, а порядок нужен всегда |
Java предоставляет мощные инструменты для сортировки. Используй их, и твой код будет чистым, быстрым и понятным. Удачи в программировании!
Вопросы и ответы
Чем Comparable отличается от Comparator?
Comparable реализуется внутри самого класса: метод
compareTo() задает один естественный порядок, и его без дополнительных настроек используют
Collections.sort(list),
Arrays.sort(array),
TreeSet и
TreeMap.
Comparator это отдельный объект, который описывает порядок снаружи: таких порядков может быть сколько угодно, и менять класс для этого не нужно. Если класс чужой (например, из библиотеки) и его код не поправить, остается только
Comparator.
Что лучше: Collections.sort(list) или list.sort()?
Результат одинаковый. С Java 8
Collections.sort(list) по документации Oracle просто вызывает
list.sort(null), то есть сортирует по естественному порядку тем же алгоритмом.
list.sort(comparator) короче и сразу принимает компаратор, поэтому в новом коде обычно пишут его. Оба метода сортируют список на месте и гарантируют стабильность.
Стабильна ли сортировка в Java?
Для объектов да:
Collections.sort(),
List.sort(),
Arrays.sort() для массивов объектов и
sorted() у упорядоченного стрима гарантированно стабильны, то есть элементы, которые компаратор считает равными, сохраняют исходный взаимный порядок. Внутри там адаптивная сортировка слиянием (TimSort). Для массивов примитивов
Arrays.sort(int[]) использует Dual-Pivot Quicksort, и вопрос стабильности там не стоит: два одинаковых числа неотличимы друг от друга.
Как отсортировать по убыванию?
Для естественного порядка передай
Comparator.reverseOrder() (или его близнеца
Collections.reverseOrder()):
list.sort(Comparator.reverseOrder()),
Arrays.sort(words, Comparator.reverseOrder()),
stream().sorted(Comparator.reverseOrder()). У своего компаратора есть метод
reversed():
Comparator.comparingInt(String::length).reversed(). Для массива примитивов вроде
int[] обратного компаратора нет: либо отсортируй по возрастанию и обходи с конца, либо используй массив
Integer[].
Почему строки сортируются не по алфавиту: слова с заглавной буквы идут раньше строчных?
Потому что
compareTo() у
String сравнивает не буквы, а их числовые коды в Unicode, а у всех заглавных букв коды меньше, чем у строчных:
"яблоко" окажется после
"Банан". Чтобы не зависеть от регистра, передай готовый компаратор
String.CASE_INSENSITIVE_ORDER. Чтобы сортировать по правилам языка, как в словаре, используй
java.text.Collator:
list.sort(Collator.getInstance(Locale.forLanguageTag("ru"))).
Что будет, если в списке есть null?
NullPointerException: ни естественный порядок, ни обычный компаратор с
null работать не умеют. Если
null в данных допустим, оберни компаратор:
list.sort(Comparator.nullsFirst(Comparator.naturalOrder())) поставит такие элементы в начало,
Comparator.nullsLast(...) в конец. Для
TreeSet и
TreeMap это тоже работает, если передать такой компаратор в конструктор.
Читайте также
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ