Что такое Comparable и Comparator?
Collections.sort(list) — принимает List и опционально Comparator. list.sort(comparator) — метод по умолчанию из Java 8. Для ArrayList оба одинаковы. Предпочитайте list.sort() —...
🟢 Junior Level
Comparable — объект САМ знает, как сравнивать себя. Comparator — внешний “судья” сравнивает два объекта.
// Comparable: внутри класса
class User implements Comparable<User> {
String name;
public int compareTo(User other) {
return this.name.compareTo(other.name); // По имени
}
}
// Comparator: отдельный "судья"
Comparator<User> byAge = (u1, u2) -> u1.age - u2.age;
// Использование:
Collections.sort(users); // Использует compareTo
users.sort(byAge); // Использует Comparator
Collections.sort(list) — принимает List и опционально Comparator. list.sort(comparator) — метод по умолчанию из Java 8. Для ArrayList оба одинаковы. Предпочитайте list.sort() — короче.
🟡 Middle Level
Comparable — natural ordering
// String, Integer, LocalDate реализуют Comparable
"apple".compareTo("banana"); // → отрицательное
LocalDate.now().compareTo(LocalDate.MAX); // → отрицательное
// Только ОДИН порядок на класс
Comparator — множество стратегий
// Java 8+: декларативный стиль
Comparator<User> comparator = Comparator
.comparing(User::getLastName) // По фамилии
.thenComparing(User::getFirstName) // Потом по имени
.thenComparingInt(User::getAge) // Потом по возрасту
.reversed(); // В обратном порядке
// Обработка null
Comparator<User> safe = Comparator
.comparing(User::getName, Comparator.nullsLast(String::compareTo));
Опасность вычитания
// ❌ Переполнение!
return o1.id - o2.id; // Может выйти за пределы int!
// ✅ Правильно:
return Integer.compare(o1.id, o2.id);
return Long.compare(o1.id, o2.id);
Например: o1.id = -2_000_000_000, o2.id = 2_000_000_000. Результат: -2_000_000_000 - 2_000_000_000 = -4_000_000_000 — за пределами int (max 2_147_483_647). Получится положительное число, сортировка неправильная.
🔴 Senior Level
Когда НЕ использовать Comparable
- Нужно несколько вариантов сортировки одного класса — используйте Comparator
- Класс из сторонней библиотеки (нельзя изменить) — используйте Comparator
- Порядок зависит от контекста/локали (сортировка строк для разных языков) — Comparator
Согласованность с equals
// TreeSet/TreeMap используют compareTo, НЕ equals!
// Если compareTo = 0 → элемент уже есть
// Даже если equals = false!
// РЕКОМЕНДУЕТСЯ:
// (x.compareTo(y) == 0) == (x.equals(y))
// Если не согласованы → потеря данных в TreeSet!
compareTo vs compare
// Comparable:
this.compareTo(other) // "я" сравниваю с "другим"
// Comparator:
compare(o1, o2) // "судья" сравнивает двоих
Timsort
// Collections.sort() = Timsort
// → Гибрид Merge Sort + Insertion Sort
// → Stable (равные сохраняют порядок)
// → O(n log n)
// → Требует транзитивности компаратора!
// Нарушение транзитивности:
// A > B, B > C, но A < C
// → IllegalArgumentException!
Timsort — алгоритм сортировки Java. Стабильный (равные элементы сохраняют исходный порядок). Транзитивность — если A > B и B > C, то обязательно A > C. Нарушение приводит к IllegalArgumentException.
Production Experience
Реальный сценарий: переполнение
// Comparator для long ID
return (int)(o1.id - o2.id); // Каст в int!
// → При больших ID → неверный знак
// → Неправильная сортировка
// Решение: Long.compare(o1.id, o2.id)
Best Practices
- Comparable = natural ordering (один на класс)
- Comparator = множественные стратегии
- Comparator.comparing → читаемо, безопасно
- Integer/Long.compare → защита от переполнения
- nullsFirst/nullsLast → обработка null
- compareTo = equals для TreeSet/TreeMap
- Транзитивность обязательна!
Резюме для Senior
- Comparable = внутренний, natural ordering
- Comparator = внешний, множественные стратегии
- comparing/thenComparing → декларативный API
- Переполнение → Integer/Long.compare
- compareTo vs equals → критично для TreeSet
- Timsort → stable, требует транзитивности
🎯 Шпаргалка для интервью
Обязательно знать:
Comparable— объект сравнивает себя сам (compareTo), один порядок на класс (natural ordering)Comparator— внешний «судья» (compare), множественные стратегии сортировкиComparator.comparing().thenComparing().reversed()— декларативный API Java 8+- Вычитание
o1.id - o2.idопасно — переполнение int; использоватьInteger.compare()/Long.compare() TreeSet/TreeMapиспользуютcompareTo, НЕequals— несогласованность = потеря данных- Timsort — стабильная сортировка O(n log n), требует транзитивности компаратора
Comparator.nullsFirst()/nullsLast()— безопасная обработка null
Частые уточняющие вопросы:
- Почему
o1.id - o2.idопасен? — При больших значениях переполнение int: -2 млрд - 2 млрд = -4 млрд (выход за пределы), результат — неправильный знак. - Что если
compareToиequalsне согласованы в TreeSet? —compareTo = 0означает «элемент уже есть», даже если `equals = false» — данные теряются. - Что такое транзитивность компаратора? — Если A > B и B > C, то обязательно A > C. Нарушение →
IllegalArgumentExceptionот Timsort. - Когда НЕ использовать Comparable? — Несколько вариантов сортировки, сторонний класс (нельзя изменить), контекстно-зависимый порядок (локаль).
Красные флаги (НЕ говорить):
- «Comparator задаёт естественный порядок класса» — нет, это делает Comparable
- «Вычитание — безопасный способ сравнения чисел» — нет, переполнение int ломает сортировку
- «TreeSet использует equals для сравнения» — нет, TreeSet использует
compareTo - «Можно нарушить транзитивность компаратора» — нет, Timsort выбросит
IllegalArgumentException
Связанные темы:
- [[25. В чём разница между Iterator и ListIterator]]
- [[31. Какие операции поддерживает интерфейс Collection]]