0Pricing
Java Academy · Урок

Пользовательский порядок в древовидных коллекциях

Передавайте Comparator в TreeMap и TreeSet, чтобы задать порядок сортировки для конкретной предметной области.

«Пользовательский порядок в древовидных коллекциях» — бесплатный урок Java Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Зачем нужен пользовательский порядок?

Древовидные коллекции (TreeMap, TreeSet) по умолчанию используют естественный порядок. Если у объектов предметной области нет естественного порядка или требуется другая сортировка, при создании передайте Comparator.

Comparator при создании TreeMap

Передайте компаратор, чтобы сортировать ключи по пользовательскому правилу — например, в обратном алфавитном порядке:

import java.util.*;

TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
map.put("banana", 2);
map.put("apple", 1);
map.put("cherry", 3);

map.forEach((k,v) -> System.out.println(k)); // cherry, banana, apple

Comparator при создании TreeSet

Передайте компаратор в TreeSet, чтобы управлять порядком сортировки независимо от естественного порядка элементов:

TreeSet<String> byLength = new TreeSet<>(
    Comparator.comparingInt(String::length)
              .thenComparing(Comparator.naturalOrder())
);
byLength.addAll(List.of("fig","apple","kiwi","date","banana"));

for (String s : byLength) System.out.print(s + " ");
// fig date kiwi apple banana

Сортировка объектов по нескольким полям

Объединяйте компараторы, чтобы сортировать сначала по основному полю, а затем по вторичному полю при совпадении значений:

record Employee(String name, String dept, int salary) {}

TreeSet<Employee> sorted = new TreeSet<>(
    Comparator.comparing(Employee::dept)
              .thenComparingInt(Employee::salary).reversed()
              .thenComparing(Employee::name)
);
sorted.add(new Employee("Alice", "Eng", 90_000));
sorted.add(new Employee("Bob",   "Eng", 85_000));
sorted.add(new Employee("Carol", "HR",  70_000));

for (Employee e : sorted) System.out.println(e.dept()+" "+e.name());

Согласованность с equals

Критически важное правило: компаратор, используемый TreeSet или TreeMap, определяет равенство элементов коллекции. Если comparator.compare(a, b) == 0, a и b считаются одним и тем же ключом — даже если a.equals(b) имеет значение false.

// Comparator ignoring case — "apple" and "APPLE" become the same key!
TreeSet<String> ci = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
ci.add("apple");
ci.add("APPLE"); // not added — same by comparator
System.out.println(ci.size()); // 1

TreeMap без учёта регистра

Распространённая практическая задача: TreeMap, в котором ключами являются строки без учёта регистра (это удобно для HTTP-заголовков и ключей конфигурации):

TreeMap<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
headers.put("Content-Type", "application/json");
headers.put("content-type", "text/html"); // overwrites!

System.out.println(headers.get("CONTENT-TYPE")); // text/html
System.out.println(headers.size()); // 1

Безопасный для null компаратор

Древовидные коллекции с естественным порядком выбрасывают NPE для ключей null. Используйте компаратор с безопасной обработкой null, чтобы разрешить null в качестве минимального ключа:

TreeMap<String, Integer> map = new TreeMap<>(
    Comparator.nullsFirst(Comparator.naturalOrder())
);
map.put(null, 0);
map.put("b", 2);
map.put("a", 1);

map.forEach((k,v) -> System.out.println(k + "=" + v));
// null=0, a=1, b=2

Пример объекта предметной области: Product по цене

Сортируйте товары по цене в TreeSet, используя имя как дополнительный критерий для сохранения уникальности:

record Product(String name, double price) {}

TreeSet<Product> catalog = new TreeSet<>(
    Comparator.comparingDouble(Product::price)
              .thenComparing(Product::name)
);
catalog.add(new Product("Widget", 9.99));
catalog.add(new Product("Gadget", 24.99));
catalog.add(new Product("Donut",  9.99));

catalog.forEach(p -> System.out.println(p.name() + " $" + p.price()));
// Donut $9.99, Widget $9.99, Gadget $24.99

Получение компаратора

Вызовите comparator() у TreeMap или TreeSet, чтобы получить пользовательский компаратор. Если используется естественный порядок, возвращается null.

TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
System.out.println(map.comparator() != null); // true

TreeMap<String, Integer> natural = new TreeMap<>();
System.out.println(natural.comparator());      // null

Композиция компараторов

Comparator в Java предоставляет объединяемые фабричные методы для удобной композиции:

Comparator<String> comp =
    Comparator.comparingInt(String::length)   // by length
              .thenComparing(Comparator.naturalOrder()); // then alphabetically

TreeSet<String> ts = new TreeSet<>(comp);
ts.addAll(List.of("go", "java", "c", "rust", "py"));
ts.forEach(s -> System.out.print(s + " "));
// c go py java rust

Когда не следует использовать пользовательский компаратор

Избегайте компаратора, не согласованного с equals, для ключей TreeMap и элементов TreeSet — это приводит к появлению сбивающих с толку «пропавших» записей. Для корректной работы во всех контекстах коллекций убедитесь, что compare(a, b) == 0 выполняется тогда и только тогда, когда a.equals(b) имеет значение true.

Быстрая проверка

В TreeSet используется компаратор, учитывающий только String::length. Что произойдёт при добавлении и "cat", и "dog"?

Повторение: пользовательская сортировка

Основные выводы:

  • Передавайте Comparator конструктору TreeMap/TreeSet, чтобы задать пользовательский порядок
  • Равенство, определяемое Comparator, управляет уникальностью элементов в древовидных коллекциях
  • Следите за согласованностью Comparator с equals, чтобы избежать неожиданного поведения
  • Используйте Comparator.nullsFirst/nullsLast для сортировки с безопасной обработкой null
  • Объединяйте условия с помощью thenComparing для сортировки по нескольким полям

Часто задаваемые вопросы

Урок «Пользовательский порядок в древовидных коллекциях» бесплатный?

Да — полный текст урока «Пользовательский порядок в древовидных коллекциях» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Пользовательский порядок в древовидных коллекциях»?

Передавайте Comparator в TreeMap и TreeSet, чтобы задать порядок сортировки для конкретной предметной области. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Пользовательский порядок в древовидных коллекциях»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. TreeMap: отсортированные пары ключ–значение
  2. Подкарты и представления диапазонов
  3. TreeSet и NavigableSet
  4. Пользовательский порядок в древовидных коллекциях
← Назад к Java Academy