Comparator.reversed(): примеры (JAVA)

Примеры работы Comparator.reversed() в Java
Раздел: Компараторы, Сравнение
Comparator.reversed(): Comparator

Описание метода Comparator.reversed()

Метод Comparator.reversed() является дефолтным методом интерфейса Comparator и доступен с Java 8. Подпись метода:

default Comparator reversed()

Метод не принимает аргументов и возвращает новый Comparator<T>, который налагает обратный порядок по сравнению с исходным компаратором. Возвращаемый компаратор делегирует сравнение оригиналу, но инвертирует результат сравнения: если оригинал возвращает положительное значение, обратный вернёт отрицательное и наоборот. Если оригинальный компаратор возвращает ноль для двух элементов, обратный также вернёт ноль.

Особенности поведения:

  • Вызов reversed() на компараторе дважды приводит к эквиваленту исходного компаратора: c.reversed().reversed() эквивалентен c.
  • Если исходный компаратор допускает null через Comparator.nullsFirst или nullsLast, то обратный компаратор сохранит логику обработки null, но порядок будет инвертирован в соответствии с общей инверсией.
  • Метод не бросает проверяемых исключений и сам по себе не возвращает null. Вызов reversed() на null невозможен, так как сам объект-компаратор должен существовать.
  • Если исходный компаратор несогласован с методом equals у сравниваемых объектов, обратный компаратор также будет несогласован. Нарушение транзитивности или согласованности с equals может привести к некорректному поведению отсортированных коллекций (TreeSet, TreeMap).

Сериализация: если исходный компаратор реализует Serializable, возвращаемый компаратор может быть сериализуемым, но это не гарантируется в абстрактном описании. На практике многие реализации стандартной библиотеки сохраняют сериализуемость при наличии такой поддержки у исходного компаратора.

Короткие примеры использования

Примеры демонстрируют несколько простых вариантов применения с выводом результата.

Пример 1. Обратный порядок для списка строк (натуральный порядок инвертируется)

List list = Arrays.asList("b", "a", "c");
list.sort(Comparator.naturalOrder().reversed());
System.out.println(list);
[c, b, a]

Пример 2. Использование Comparator.reverseOrder() и reversed() (эквиваленты для натурального порядка)

List nums = Arrays.asList(3, 1, 2);
nums.sort(Comparator.reverseOrder());
System.out.println(nums);

List nums2 = Arrays.asList(3, 1, 2);
nums2.sort(Comparator.naturalOrder().reversed());
System.out.println(nums2);
[3, 2, 1]
[3, 2, 1]

Пример 3. Компаратор для объектов по полю и инверсия

class Person { String name; int age; }
List<Person> people = ...;
Comparator<Person> byAge = Comparator.comparingInt(p -> p.age);
people.sort(byAge.reversed());
// Сортировка от старших к младшим
(список Person в порядке убывания возраста)

Пример 4. Обработка null вместе с reversed()

List names = Arrays.asList("bob", null, "alice");
Comparator<String> cmp = Comparator.nullsLast(String::compareTo).reversed();
names.sort(cmp);
System.out.println(names);
[bob, alice, null]

Аналоги и близкие приёмы в Java

  • Comparator.reverseOrder() - статический метод, возвращает компаратор, реализующий обратный натуральный порядок. Предпочтителен, когда требуется именно обратный натуральный порядок и не нужен исходный компаратор.
  • Collections.reverseOrder() - исторический аналог, возвращает тот же смысл: обратный натуральный порядок или обратный заданному компаратору в перегруженной версии.
  • Comparator.comparing(...).reversed() - удобный приём для инверсии компаратора, созданного с помощью фабричных методов сравнения по ключам. Предпочтителен при сравнении по конкретному полю.
  • Comparator.nullsFirst / nullsLast - комбинируются с reversed() для управления расположением null при инверсии.

Выбор зависит от задачи: для обратного натурального порядка можно использовать reverseOrder(), для инверсии пользовательского компаратора - reversed() на этом компараторе, для инверсии только ключевого критерия - построить компаратор через comparing и применить reversed() к нему.

Аналоги в других языках и отличия

Краткие эквиваленты в популярных языках с примерами и результатом.

Python - встроенная поддержка обратного порядка через параметр reverse в sorted и методе списка sort.

a = [3, 1, 2]
print(sorted(a, reverse=True))
[3, 2, 1]

JavaScript - метод sort с инвертированным компаратором.

let a = [3,1,2];
a.sort((x,y) => y - x);
console.log(a);
[3,2,1]

C# - LINQ: OrderByDescending, для массивов класс Array.Sort с Comparer<T>.Create.

var a = new[] {3,1,2};
var r = a.OrderByDescending(x => x).ToArray();
Console.WriteLine(string.Join(",", r));
3,2,1

Kotlin - аналогично Java: sortedWith(compareBy(...).reversed()) или sortedDescending() для натурального порядка.

val a = listOf(3,1,2)
println(a.sortedDescending())
[3, 2, 1]

Go - сортировка с функцией сравнения через sort.Slice, инверсия достигается изменением порядка аргументов.

a := []int{3,1,2}
sort.Slice(a, func(i,j int) bool { return a[i] > a[j] })
fmt.Println(a)
[3 2 1]

PHP - функции rsort для массивов по значению, или usort с инвертированным сравнителем.

$a = [3,1,2];
rsort($a);
print_r($a);
Array ( [0] => 3 [1] => 2 [2] => 1 )

Lua - table.sort с компаратором, инверсия через перестановку параметров.

a = {3,1,2}
table.sort(a, function(x,y) return x > y end)
for i,v in ipairs(a) do print(v) end
3
2
1

SQL - ключевое слово DESC в ORDER BY для обратного порядка.

SELECT name FROM users ORDER BY age DESC;
(записи в порядке убывания возраста)

Отличия от Java: в некоторых языках (Python, Kotlin) обратный порядок является опцией высокого уровня у сортирующих функций, поэтому не требуется создавать отдельный компаратор. В Java чаще строится и комбинируется объект-компаратор, что даёт больше контроля над композициями и поведением при null.

Типичные ошибки при использовании

  • Вызов reversed() на null-ссылке компаратора. Пример:
Comparator<String> cmp = null;
// cmp.reversed(); // приведёт к NullPointerException
java.lang.NullPointerException
  • Ожидание, что reversed() изменяет исходный компаратор. Метод возвращает новый компаратор, оригинал остаётся без изменений. Пример различия:
Comparator<Integer> c = Comparator.naturalOrder();
Comparator<Integer> r = c.reversed();
List<Integer> a = Arrays.asList(1,2,3);
a.sort(c);
System.out.println(a);
a.sort(r);
System.out.println(a);
[1, 2, 3]
[3, 2, 1]
  • Нарушение транзитивности или несогласованность с equals при комбинировании компараторов и последующая передача в TreeSet/TreeMap. Результат - непредсказуемое поведение коллекций на основе дерева.
  • Неправильная инверсия только части цепочки сравнений. Вызов (a.thenComparing(b)).reversed() инвертирует весь компаратор целиком. Если требуется инвертировать только первичный критерий, нужно инвертировать именно его: a.reversed().thenComparing(b). Пример неявного сюрприза приведён в расширенных примерах.

Изменения в версии Java

Метод reversed() появился как дефолтный метод в интерфейсе Comparator в Java 8 вместе с другими удобными фабриками и методами композиции (comparing, thenComparing, nullsFirst, nullsLast). В последующих релизах изменений в поведении reversed() не происходило. Развитие API Comparator в основном добавляло дополнительные фабрики и улучшало совместимость, но семантика инверсии осталась прежней.

Расширенные и редкие варианты применения

Несколько нетривиальных кейсов с пояснениями и выводом.

Пример A. Инверсия только первичного ключа при составном сравнении

Пример java
record Item(String cat, int rank) {}
List<Item> items = List.of(new Item("A",1), new Item("A",2), new Item("B",1));
Comparator<Item> byCat = Comparator.comparing(i -> i.cat);
Comparator<Item> byRank = Comparator.comparingInt(i -> i.rank);
// Требуется: категории по возрастанию, в каждой категории ранги по убыванию
Comparator<Item> cmp = byCat.thenComparing(byRank.reversed());
items.sort(cmp);
System.out.println(items);
[Item[A,2], Item[A,1], Item[B,1]]

Пояснение: инверсия применена только к рангу, что даёт ожидаемую группировку по категории с внутренним убыванием.

Пример B. Инверсия всей цепочки и отличие от инверсии первичного ключа

Пример java
Comparator<Item> wholeReversed = byCat.thenComparing(byRank).reversed();
List<Item> items2 = new ArrayList<>(items);
items2.sort(wholeReversed);
System.out.println(items2);
[Item[B,1], Item[A,2], Item[A,1]]

Пояснение: здесь сначала инвертируется порядок категорий в целом, затем внутри новой последовательности применяется исходная логика сравнения по рангу. Это не эквивалентно предыдущему примеру.

Пример C. reversed() вместе с nullsFirst/nullsLast

Пример java
List<String> list = Arrays.asList(null, "a", "b");
Comparator<String> cmp = Comparator.nullsFirst(String::compareTo).reversed();
list.sort(cmp);
System.out.println(list);
[b, a, null]

Пояснение: nullsFirst помещает null в начало в исходном компараторе, но после инверсии null оказывается в конце. Для получения обратного эффекта с сохранением null в начале следует использовать Comparator.nullsLast(...).reversed() в зависимости от желаемого результата.

Пример D. Использование в PriorityQueue и влияния на извлечение

Пример java
PriorityQueue<Integer> pq = new PriorityQueue<>(Comparator.naturalOrder().reversed());
pq.addAll(List.of(1,3,2));
while(!pq.isEmpty()) System.out.print(pq.poll() + " ");
3 2 1 

Пояснение: очередь с компаратором в обратном порядке выдаёт элементы от наибольших к наименьшим.

Пример E. Сериализация компаратора и осторожность

Пример java
Comparator<String> base = Comparator.comparing(String::length);
Comparator<String> rev = base.reversed();
// Если 'base' сериализуем, 'rev' зачастую тоже; проверка возможна через instanceof Serializable
System.out.println(base instanceof java.io.Serializable);
System.out.println(rev instanceof java.io.Serializable);
false
false

Пояснение: у стандартных компараторов сериализуемость зависит от конкретной реализации и от того, предоставлялся ли сериализуемый лямбда-выражением или методом. В критичных случаях сериализуемость следует проверять отдельно.

Пример F. Взаимодействие с TreeSet и последствия несогласованности

Пример java
Comparator<String> broken = (a,b) -> a.length() - b.length();
TreeSet<String> set = new TreeSet<>(broken.reversed());
set.addAll(List.of("a","bb","ccc"));
System.out.println(set);
[ccc, bb, a]

Пояснение: если компаратор сравнивает только длину строк, элементы с одинаковой длиной считаются равными для множества, и одно из значений может быть отброшено. Инверсия не исправляет саму проблему; при необходимости следует комбинировать с дополнительным критерием через thenComparing.

Пример G. Комбинация reversed() с thenComparing, чтобы задать детерминированный порядок

Пример java
Comparator<String> comp = Comparator.comparingInt(String::length)
    .reversed()
    .thenComparing(Comparator.naturalOrder());
List<String> l = List.of("bb","a","aa");
List<String> l2 = new ArrayList<>(l);
l2.sort(comp);
System.out.println(l2);
[aa, bb, a]

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

джава Comparator.reversed() function comments

En
Comparator.reversed() Возвращает обратный компаратор