Пользовательский порядок в древовидных коллекциях
Передавайте 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, appleComparator при создании 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()); // 1TreeMap без учёта регистра
Распространённая практическая задача: 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 — локальная установка не требуется.
Все уроки этого курса
- TreeMap: отсортированные пары ключ–значение
- Подкарты и представления диапазонов
- TreeSet и NavigableSet
- Пользовательский порядок в древовидных коллекциях