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

Geo*_* Z. 8 java collections lambda optional comparator

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

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们假设getLastName返回一个可选的:

public Optional<String> getLastName() {
    return Optional.ofNullable(lastName);
}
Run Code Online (Sandbox Code Playgroud)

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

第一个谷歌搜索将我们指向了这个答案。基于此答案,我们可以通过以下方式对人员进行排序:

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

我的问题是,是否可以像 Comparator.comparing 一样使用函数(键提取器)进行这种排序?

我的意思是(首先或最后不关心缺失值):

persons.sort(OptionalComparator.comparing(Person::getLastName));
Run Code Online (Sandbox Code Playgroud)

如果我们查看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));
    };
}
Run Code Online (Sandbox Code Playgroud)

我尝试了多种方法使其返回 anOptionalComparator而不是 simple Comparator,但是我尝试过的并且对我有意义的所有内容都无法编译。甚至有可能实现这样的目标吗?我猜无法实现类型安全,因为即使是 Oracle 也会comparing抛出类型安全警告。

我在 Java 8 上。

Sla*_*law 8

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

接受从类型中提取排序键的函数T,并返回Comparator<T>使用指定的排序键进行比较的Comparator

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

persons.sort(comparing(Person::getLastName, comparing(Optional::get)));
Run Code Online (Sandbox Code Playgroud)

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

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

如果您需要大量执行此操作,请考虑以类似于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));
}
Run Code Online (Sandbox Code Playgroud)

然后可以像这样使用:

persons.sort(comparing(Person::getLastName, emptyLast()));
Run Code Online (Sandbox Code Playgroud)

1. 根据@Holger提供的建议简化示例代码。如果好奇,请查看编辑历史记录以查看代码之前的样子。

  • 以这种方式混合三元运算符和“if”语句看起来不一致。如果仅使用三元运算符,则可以使用简洁的表达式 lambda 语法。但是,另一方面,您可以轻松使用 `public static &lt;T&gt; Comparator&lt;Optional&lt;T&gt;&gt; emptyFirst(Comparator&lt;? super T&gt; comparator) { return Comparator.comparing(o -&gt; o.orElse(null), Comparator.nullsFirst(比较器)); }` 使用与 `nullsFirst` 相同的语义(传递 `null` 比较器意味着“仅将空选项移到前面”)。 (2认同)
  • 如果您不喜欢嵌套三元运算符,您可以使用 `(o1, o2) -&gt; o1.isPresent() &amp;&amp; o2.isPresent()? comparator.compare(o1.get(), o2.get()): Boolean.compare(o1.isPresent(), o2.isPresent())` 代替。 (2认同)