Comparable<T> реализует сам класс: int compareTo(T other) — отрицательное, ноль или положительное. Это его естественный порядок, используемый Collections.sort, TreeSet, Arrays.sort без компаратора.
Comparator<T> — отдельный объект сравнения. Передаётся в sort, TreeMap, stream.sorted, max. Удобные фабрики:
users.sort(Comparator.comparing(User::lastName)
.thenComparing(User::firstName)
.thenComparing(User::age, Comparator.reverseOrder()));
nullsFirst, nullsLast, reversed().
Согласованность с equals. Порядок согласован, если a.compareTo(b) == 0 тогда и только тогда, когда a.equals(b). Сортированные коллекции (TreeSet, TreeMap) определяют одинаковость только через сравнение. Если компаратор считает равными разные по equals объекты (сравнение только по фамилии), TreeSet молча отбросит второго Петрова, хотя HashSet хранил бы обоих. Известный пример несогласованности в JDK — BigDecimal: 2.0 и 2.00 не равны по equals, но compareTo даёт 0.
Ловушки: сравнение через вычитание (return a.x - b.x) переполняется для больших значений — Integer.compare; контракт требует транзитивности и антисимметричности, нарушение даёт IllegalArgumentException: Comparison method violates its general contract в TimSort.