带有密钥提取器的 Optional<T> 的比较器,例如 java.util.Comparator.comparing

Comparator for Optional<T> with key extractor, like java.util.Comparator.comparing

考虑以下示例,我们根据姓氏对人员进行排序:

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("Whosebuged");
        Person p3 = new Person(null);
        List<Person> persons = Arrays.asList(p3, p2, p1);
        persons.sort(Comparator.comparing(Person::getLastName));
    }
}

现在,我们假设 getLastName return 是一个可选的:

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

显然 persons.sort(Comparator.comparing(Person::getLastName)); 不会编译,因为 Optional(类型 getLastName returns)不是可比较的。然而,它所持有的价值是。

第一个 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));
    };
}

我尝试了多种方法使它 return 成为 OptionalComparator 而不是简单的 Comparator,但我尝试过的所有对我有意义的东西都无法编译。 甚至有可能实现这样的目标吗? 我猜类型安全无法实现,因为即使是 Oracle 的 comparing 也会抛出类型安全警告。

我在 Java 8.

您可以使用 Comparator#comparing(Function,Comparator):

Accepts a function that extracts a sort key from a type T, and returns a Comparator<T> that compares by that sort key using the specified Comparator.

这是一个基于您问题中的代码的示例:

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

基本上这是使用嵌套键提取器来最终比较表示姓氏的 String 对象。请注意,如果 Optional 为空,这将导致抛出 NoSuchElementException。您可以创建一个更复杂的 Comparator 来处理空 Optionals1:

// 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:[=28= 的方式创建实用程序方法]

// 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 提供的建议简化了示例代码。如果好奇的话,看看编辑历史,看看以前的代码是什么样子。

帮助很大,根据他的回答,我得到了我想要的。然而,正如他提到的

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));