Сортировка коллекций

Сортировка в Java строится на двух интерфейсах:

  • Comparable<T> — «естественный порядок» самого типа. Метод compareTo(T other). Например, Integer, String, LocalDate уже реализуют Comparable.

  • Comparator<T> — внешняя стратегия сравнения. Передаётся в сортировку как параметр. Удобно для нескольких разных порядков для одного и того же класса.

Контракт compareTo/compare: возвращает отрицательное число, если a < b; положительное, если a > b; ноль, если равны.

Утилиты для сортировки:

  • Collections.sort(List) / Collections.sort(List, Comparator)

  • List.sort(Comparator) — современный способ для списков

  • Arrays.sort(...)

  • Stream.sorted() / Stream.sorted(Comparator)

Пример 1. Естественный порядок (Comparable)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortNatural {
    public static void main(String[] args) {
        List<Integer> data = new ArrayList<>();
        data.add(42); data.add(7); data.add(19); data.add(3);
        Collections.sort(data);
        System.out.println("По возрастанию: " + data);

        Collections.sort(data, Collections.reverseOrder());
        System.out.println("По убыванию: " + data);
    }
}

Output:

По возрастанию: [3, 7, 19, 42]
По убыванию: [42, 19, 7, 3]

Пример 2. Свой класс с Comparable

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortComparable {
    static class Sensor implements Comparable<Sensor> {
        String name;
        int distance;
        Sensor(String n, int d) { name = n; distance = d; }
        @Override public int compareTo(Sensor o) {
            return Integer.compare(this.distance, o.distance);
        }
        @Override public String toString() { return name + "=" + distance; }
    }

    public static void main(String[] args) {
        List<Sensor> list = new ArrayList<>();
        list.add(new Sensor("front", 45));
        list.add(new Sensor("left", 12));
        list.add(new Sensor("right", 87));
        Collections.sort(list);
        System.out.println(list);
    }
}

Output:

[left=12, front=45, right=87]

Пример 3. Comparator.comparing и thenComparing

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortComparator {
    record Kit(String name, int price, int year) {}

    public static void main(String[] args) {
        List<Kit> kits = new ArrayList<>();
        kits.add(new Kit("Phobo", 25000, 2024));
        kits.add(new Kit("UNO",   12000, 2023));
        kits.add(new Kit("UNO",   12000, 2024));
        kits.add(new Kit("ESP32", 18000, 2024));

        Comparator<Kit> byNameThenYear =
            Comparator.comparing(Kit::name).thenComparingInt(Kit::year);

        kits.sort(byNameThenYear);
        kits.forEach(System.out::println);
    }
}

Output:

Kit[name=ESP32, price=18000, year=2024]
Kit[name=Phobo, price=25000, year=2024]
Kit[name=UNO, price=12000, year=2023]
Kit[name=UNO, price=12000, year=2024]

Пример 4. reversed и nullsFirst

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class SortReverseNulls {
    public static void main(String[] args) {
        List<String> data = new ArrayList<>(Arrays.asList("Phobo", null, "UNO", "ESP32", null));
        data.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println("nullsFirst: " + data);

        data.sort(Comparator.nullsLast(Comparator.<String>naturalOrder().reversed()));
        System.out.println("nullsLast+reversed: " + data);
    }
}

Output:

nullsFirst: [null, null, ESP32, Phobo, UNO]
nullsLast+reversed: [UNO, Phobo, ESP32, null, null]

Пример 5. Stream.sorted и сбор в новый список

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class SortStream {
    public static void main(String[] args) {
        List<String> kits = Arrays.asList("UNO", "Phobo", "ESP32", "Nano");
        List<String> byLen = kits.stream()
            .sorted(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()))
            .collect(Collectors.toList());
        System.out.println(byLen);
    }
}

Output:

[UNO, Nano, ESP32, Phobo]

Пример 6. TreeSet с Comparator

import java.util.Comparator;
import java.util.TreeSet;

public class SortTreeSet {
    public static void main(String[] args) {
        TreeSet<String> kits = new TreeSet<>(
            Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder())
        );
        kits.add("UNO");
        kits.add("Phobo");
        kits.add("ESP32");
        kits.add("Nano");
        kits.add("Mega");
        System.out.println(kits);
    }
}

Output:

[UNO, Mega, Nano, ESP32, Phobo]

Подводные камни

  • compareTo обязан быть согласован с ``equals``, иначе TreeSet/TreeMap будут вести себя странно.

  • Никогда не пишите return a.value - b.value; для int — возможен переполнение. Используйте Integer.compare(a, b).

  • Collections.sort стабилен (равные элементы сохраняют относительный порядок).

  • Сортировать List, возвращённый Arrays.asList, можно (sort не меняет размер), а вот List.of(...) — нет (бросит UnsupportedOperationException).

  • Comparator.naturalOrder() требует, чтобы тип реализовывал Comparable.

  • При сравнении строк по содержимому — учтите регистр (String.CASE_INSENSITIVE_ORDER) и локаль (Collator).

См. также

Примечание

Материал подготовлен на основе официального руководства Oracle The Collections Framework и распространяется на условиях Oracle Free Documentation License. Тексты переписаны своими словами, примеры кода — оригинальные.