Компаратор для необязательного <T>с экстрактором ключей, например java .util.Comparator.comparing - PullRequest
7 голосов
/ 08 мая 2020

Рассмотрим следующий пример, в котором мы сортируем людей по фамилии:

public class ComparatorsExample {

    public static class Person {
        private String lastName;

        public Person(String lastName) {
            this.lastName = lastName;
        }

        public String getLastName() {
            return lastName;
        }

        @Override
        public String toString() {
            return "Person: " + lastName;
        }
    }

    public static void main(String[] args) {
        Person p1 = new Person("Jackson");
        Person p2 = new Person("Stackoverflowed");
        Person p3 = new Person(null);
        List<Person> persons = Arrays.asList(p3, p2, p1);
        persons.sort(Comparator.comparing(Person::getLastName));
    }
}

Теперь предположим, что getLastName возвращает необязательное значение:

public Optional<String> getLastName() {
    return Optional.ofNullable(lastName);
}

Очевидно persons.sort(Comparator.comparing(Person::getLastName)); не будет компилироваться, поскольку Optional (возвращается тип getLastName) не является сопоставимым. Однако его значение равно.

Первый поиск Google указывает нам на этот ответ . Основываясь на этом ответе, мы можем отсортировать людей, выполнив:

List<Person> persons = Arrays.asList(p3, p2, p1);
OptionalComparator<String> absentLastString = absentLastComparator(); //type unsafe
persons.sort((r1, r2) -> absentLastString.compare(r1.getLastName(), r2.getLastName()));

Мой вопрос: можно ли использовать такую ​​сортировку с использованием функции (экстрактора ключей), как Comparator.comparing?

Я имею в виду что-то вроде (не заботясь об отсутствующих значениях первым или последним):

persons.sort(OptionalComparator.comparing(Person::getLastName));

Если мы посмотрим на Comparator.comparing , мы увидим следующий код :

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable) (c1, c2) -> {
        return keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    };
}

Я пробовал несколькими способами заставить его возвращать OptionalComparator вместо простого Comparator, но все, что я пробовал и имело для меня смысл, не могло быть скомпилировано. Возможно ли вообще что-то подобное? Я полагаю, что типобезопасность не может быть достигнута, поскольку даже Oracle comparing выдает предупреждение о типобезопасности.

Я на Java 8.

Ответы [ 2 ]

6 голосов
/ 08 мая 2020

Вы можете использовать Comparator#comparing(Function,Comparator):

Принимает функцию, которая извлекает ключ сортировки из типа T и возвращает Comparator<T>, которое сравнивает ключ сортировки с использованием указанного Comparator.

Вот пример, основанный на коде в вашем вопросе:

persons.sort(comparing(Person::getLastName, comparing(Optional::get)));

В основном это использование вложенных экстракторов ключей для окончательного сравнения String объектов, представляющих фамилии. Обратите внимание, что это вызовет выдачу NoSuchElementException, если любой из Optional пуст. Вы можете создать более сложный Comparator для обработки пустых Optional s 1 :

// sort empty Optionals last
Comparator<Person> comp =
    comparing(
        Person::getLastName,
        comparing(opt -> opt.orElse(null), nullsLast(naturalOrder())));
persons.sort(comp);

Если вам нужно делать это часто, рассмотрите возможность создания служебных методов аналогичным образом на Comparator#nullsFirst(Comparator) и Comparator#nullsLast(Comparator) 1 :

// empty first, then sort by natural order of the value
public static <T extends Comparable<? super T>> Comparator<Optional<T>> emptyFirst() {
  return emptyFirst(Comparator.naturalOrder());
}

// empty first, then sort by the value as described by the given
// Comparator, where passing 'null' means all non-empty Optionals are equal
public static <T> Comparator<Optional<T>> emptyFirst(Comparator<? super T> comparator) {
  return Comparator.comparing(opt -> opt.orElse(null), Comparator.nullsFirst(comparator));
}

// empty last, then sort by natural order of the value
public static <T extends Comparable<? super T>> Comparator<Optional<T>> emptyLast() {
  return emptyLast(Comparator.naturalOrder());
}

// empty last, then sort by the value as described by the given
// Comparator, where passing 'null' means all non-empty Optionals are equal
public static <T> Comparator<Optional<T>> emptyLast(Comparator<? super T> comparator) {
  return Comparator.comparing(opt -> opt.orElse(null), Comparator.nullsLast(comparator));
}

Что затем можно использовать как:

persons.sort(comparing(Person::getLastName, emptyLast()));

1. Пример кода упрощен на основе предложений, предоставленных @ Holger . Если интересно, посмотрите историю изменений, чтобы увидеть, как выглядел код раньше.

1 голос
/ 08 мая 2020

Ответ Слоу очень помог, и на основе его ответа я получил именно то, что хотел. Однако, как он упоминает,

persons.sort(comparing(Person::getLastName, comparing(Optional::get));

вызовет исключение, если значение Optional отсутствует. Тем самым мы упускаем большую часть OptionalComparator точки. Но, к счастью, его можно преобразовать в:

persons.sort(comparing(Person::getLastName, absentFirstComparator()));

, где будет обрабатываться случай отсутствия значения.

Также можно создать эти два метода:

public static <T, U extends Optional> Comparator<T> absentFirst(Function<? super T, ? extends U> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T>) (c1, c2) -> absentFirstComparator().compare(keyExtractor.apply(c1),
            keyExtractor.apply(c2));
}

public static <T, U extends Optional> Comparator<T> absentLast(Function<? super T, ? extends U> keyExtractor) {
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T>) (c1, c2) -> absentLastComparator().compare(keyExtractor.apply(c1),
            keyExtractor.apply(c2));
}

и, наконец, я получаю именно то, что хочу:

persons.sort(absentLast(Person::getLastName));
...