


Comparator описывает внешний порядок сравнения объектов. Через comparing выбирают основной ключ, через thenComparing добавляют следующий, а через nullsLast задают место отсутствующих значений. Следующий ключ используется только при равенстве предыдущего.
В учебном примере Mentoring.digital задачи нужно расположить сначала по возрастанию приоритета, затем по названию. Чем меньше число приоритета, тем выше задача в списке. Названия сравниваем обычным порядком Java-строк, без правил русского словаря.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class SortDemo {
record Task(int priority, String name) {}
public static void main(String[] args) {
var tasks = new ArrayList<>(List.of(
new Task(2, "Docs"),
new Task(1, "Tests"),
new Task(1, "Build")
));
Comparator<Task> order = Comparator
.comparingInt(Task::priority)
.thenComparing(Task::name);
tasks.sort(order);
tasks.forEach(System.out::println);
}
}
Пример с record рассчитан на Java 16 и новее. Получится порядок Build, Tests, Docs. Задача Docs остаётся последней, хотя её название идёт раньше Tests: основной ключ уже определил порядок.
tasks.sort изменяет сам список. Если исходный порядок нужен для другой операции, создайте new ArrayList<>(tasks) и сортируйте копию. Копирование списка не создаёт глубокие копии его элементов.
Comparator<Task> byFields = Comparator
.comparingInt(Task::priority)
.thenComparing(Task::name,
Comparator.nullsLast(Comparator.naturalOrder()));
Comparator<Task> withNullTasks = Comparator.nullsLast(byFields);
Первый nullsLast относится к названию. Задачи с отсутствующим названием попадут в конец своей группы приоритета. Внешний nullsLast обрабатывает ситуацию, когда элемент списка целиком равен null. Это две разные проверки. Если nullable только поле, внешняя обёртка не нужна.
Comparator<Task> priorityDescending = Comparator
.comparingInt(Task::priority)
.reversed()
.thenComparing(Task::name,
Comparator.nullsLast(Comparator.naturalOrder()));
Здесь развёрнут только приоритет, а названия остаются по возрастанию. Если вызвать reversed() в конце всей цепочки, развернутся все её критерии. Разворот компаратора с nullsLast также поменяет место null. Чтобы null-объекты всегда оставались последними, внешнюю обёртку применяйте уже к нужному окончательному порядку.
Не пишите сравнение целых чисел как a.priority() - b.priority(): при больших значениях возможно переполнение. Используйте comparingInt или Integer.compare. Компаратор должен давать согласованный порядок, включая равные значения и повторные сравнения.
Проверьте разные приоритеты, одинаковый приоритет с разными именами, одинаковые ключи и допустимые null. Если тот же компаратор используется в TreeSet, помните: результат сравнения ноль определяет эквивалентность элементов для множества. Два разных объекта с одинаковыми ключами могут не сохраниться как две записи.
Для системной практики с коллекциями есть курсы Java с наставником. Правила сортировки реальных объектов можно обсудить с менторами по Java.