Comparator.comparing 相对于 Comparator 有什么用

据我了解 Comparator 是一个功能接口,用于将两个对象与 int compare(T o1, T o2) 作为带有两个参数的抽象函数进行比较。但也有一个函数 Comparator.comparing(s->s) 可以采用只有一个输入参数的 lambda 函数。例如使用流对集合进行排序

        List<String> projects=Arrays.asList("abc","def","sss","aaa","bbb");
        projects.stream().sorted((x,y)->y.compareTo(x)).forEach(s->System.out.println(s));
        projects.stream().sorted(Comparator.comparing(s->s)).forEach(s->System.out.println(s));

sorted 方法将 Comparator 作为参数。所以我能够理解第一个 lambda 表达式,但我想知道 Comparator.comparing(s->s) 的使用,即 Comparator.comparing() 是用于将单参数 lambda 表达式转换为双参数,还是它还有其他用途。还请解释以下函数声明的部分。

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
stack overflow What is the use of Comparator.comparing in respect to Comparator
原文答案
author avatar

接受的答案

Comparator.comparing() 是否用于将单参数 lambda 表达式转换为双参数?

是的,你可以这样想。

在对事物进行排序时,您应该指定“给定两个事物 ab ,它们中的哪一个更大,或者它们相等?”使用 Comparator<T>ab 是它有 2 个 lambda 参数的原因,并且您返回一个整数来表示您对该问题的答案。

然而,一个更方便的方法是指定“给定一个事物 x ,你想对 x 的哪一部分进行排序?”。这就是您可以使用 bykeyExtractor 参数所做的事情。

相比:

Comparator.comparing

后者显然更加简洁和直观。我们倾向于考虑 ```
/
given two people, a and b, the comparison result between a and b is the
comparison result between a's name and b's name
/
Comparator personNameComparator =
(a, b) -> a.getName().compareTo(b.getName());

/
given a person x, compare their name
/
Comparator personNameComparator =
Comparator.comparing(x -> x.getName()); // or Person::getName


至于  `what`  的声明:

 `comparing` 

public static <T, U extends Comparable<? super U>> Comparator comparing(
Function<? super T, ? extends U> keyExtractor)

`` 部分首先声明了两个泛型类型参数 -<T, U extends Comparable<? super U>>是比较器比较的(在上面的例子中是T),而Person是您实际比较的类型({JXM= } 在上述情况下),因此它是U` 。

String 是您传入的参数,例如 extends Comparable ,它应该回答“当给定 keyExtractor 时,您想要比较的 x -> x.getName() 是什么?”的问题。

如果您对 TU 感到困惑,请阅读 ? super

如果您还没有意识到, ? extends 的实现基本上可以归结为:

What is PECS?


答案:

作者头像

Comparator#compare(T o1, T o2) 比较两个对象并根据此条件返回一个整数值:

  • 如果 o1 < o2 则为负值
  • 如果 o1 > o2 则为正值
  • 如果它们相等,则为零。

    Comparator.comparing(Function<? super T, ? extends U> key) 返回一个按该排序键进行比较的 Comparator<T>

主要区别在于 compare 方法提供单点比较,而 comparing 链接到其他函数以提供多点比较。

假设你有一个类 Person

public class Person implements Comparable<Person> {
    private String firstName;
    private String lastName;
    private int age;
    // rest of class omitted
}

如果您使用 Person 比较两个 p1 实例 p2compare(p1, p2) ,则将执行比较,并且将根据类规定的某些自然顺序对两个对象进行排序。相反,如果您想使用 comparing() 比较相同的两个实例,则将根据您根据类的某些属性选择比较的任何标准来执行比较。例如: Comparator.comparing(Person::getFirstName)

因为 comparing 返回一个 Comparator 而不是一个值,正如我之前所说,您可以链接多个比较。例如: Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName);

至于返回类型 <T, U extends Comparable<? super U>> Comparator<T> 的含义,可以找解释 here

我想补充一点,类必须具有可比性才能使 compare(T o1, T o2) 工作。字符串对象是可比较的,因为它们实现了这个接口。也就是说,如果一个类不是 Comparable ,您仍然可以使用 comparing 方法,因为正如我所说,您可以选择要用于比较的类的哪个属性,并且这些属性很可能具有可比性(即上面示例中人名或年龄的字符串)。

作者头像

想象一下你有你的自定义对象:

public class Person {

  private String firstName;
  private String lastName;

  //getters and setters
}

现在假设您必须通过名字来比较人们。一种选择是像这样编写比较器:

Comparator<Person> comparator2 = (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName());

这应该很明显 - 获取一个人的名字,并将其与另一个人的名字进行比较。其他选项是使用 Comparator.comparing(Function<? super T,? extends U> keyExtractor) 编写相同的比较器。它看起来像这样:

Comparator<Person> comparator1 = Comparator.comparing(p -> p.getFirstName());

甚至更好的方法参考:

Comparator<Person> comparator1 = Comparator.comparing(Person::getFirstName);

您在此处提供的单个参数是 Function ,它接受​​单个参数,这就是您的 lambda 也使用单个参数的原因。这里的函数用作键提取器 - 它从您的对象中提取要比较的键。这就像说,从第一个对象获取名字并将其与第二个对象的名字进行比较。

请注意,像这样提取的键必须Comparable 。在上面的示例中,提取的键是 String ,它实现了可比性。

作者头像

TL;DR

只要 keyExtrator 也实现 object ,就提供 field 来比较对象的 field 或任何 Comparable 。返回的是使用 Comparator fields 方法的 compareTo

故事的其余部分

这是来自 Comparator 接口的完整方法

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) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

首先, Comparable 允许对象通过实现 Comparable interfaceint compareTo(ob) 方法来提供自然排序。所以一个对象可以将自己与它自己的类(或者可能是祖先类)的另一个实例进行比较。

Comparator 是一种允许比较未实现 Comparable interface 的相关对象的方法。它由 compare(ob1, ob2) 调用。

上面显示的接口允许返回一个 Comparator ,它使用了比较对象的 Comparable 实现。但它也允许通过 keyExtractor 获得该对象的一部分(例如字段)。然后返回必须实现 key 的已提取 Comparable 的比较器。

什么说明这些后续字段也必须实现 Comparable ?看签名。如果 keyExtractorUU extends Comparable<? super U> ,则返回类型。

以下是一些带有解释的示例。

class Bar {
    int val;
    public int getVal(){
        return val;
    }
}

class FooBar implements Comparable<FooBar> {
    String svalue;
    Bar bar;
    int value;

    public FooBar(int v, Bar b, String svalue) {
        this.value = v;
        this.bar = b;
        this.svalue = svalue;
    }

    public String getSValue() {
        return svalue;
    }

    public int getValue() {
        return value;
    }

    public Bar getBar() {

        return bar;
    }

    public int compareTo(FooBar b) {
        return value < b.value ? -1 : value > b.value ? 1 : 0;
    }

    public String toString() {
        return "%s, %s, %s".formatted(value, bar, svalue);
    }
}

List<FooBar> list = new ArrayList<>(
        List.of(new FooBar(1, new Bar(), "11"),
                new FooBar(2, new Bar(), "AA"),
                new FooBar(3, new Bar(), "BA"),
                new FooBar(4, new Bar(), "CC"),
                new FooBar(5, new Bar(), "2A"),
                new FooBar(6, new Bar(), "AA11"),
                new FooBar(7, new Bar(), "11AA"),
                new FooBar(8, new Bar(), "AAG")));

FooBar 的自然排序

list.sort(null);  //null says use natural ordering.
list.forEach(System.out::println);

印刷

1, stackOverflow.Bar@681a9515, 11
2, stackOverflow.Bar@3af49f1c, AA
3, stackOverflow.Bar@19469ea2, BA
4, stackOverflow.Bar@13221655, CC
5, stackOverflow.Bar@2f2c9b19, 2A
6, stackOverflow.Bar@31befd9f, AA11
7, stackOverflow.Bar@1c20c684, 11AA
8, stackOverflow.Bar@1fb3ebeb, AAG

对字符串 svalue 排序

Comparator<FooBar> comp = Comparator.comparing(FooBar::getSValue);
list.sort(comp);  // sort on svalue

印刷

1, stackOverflow.Bar@33c7353a, 11
7, stackOverflow.Bar@681a9515, 11AA
5, stackOverflow.Bar@3af49f1c, 2A
2, stackOverflow.Bar@19469ea2, AA
6, stackOverflow.Bar@13221655, AA11
8, stackOverflow.Bar@2f2c9b19, AAG
3, stackOverflow.Bar@31befd9f, BA
4, stackOverflow.Bar@1c20c684, CC

按对象栏排序

Comparator<FooBar> comp = Comparator.comparing(FooBar::getBar); // oops!

这行不通。甚至不能在这里定义比较器,因为 Bar 没有按照签名的要求实现 Comparable 。为什么允许 svalue ?因为它是一个 String 并且 String 类实现了 Comparable

但一切都没有丢失。可以使用 Integer.compare 完成以下操作,因为 Bar's 值是一个 int。

Comparator<FooBar> comp1 = (f1,f2)-> {
          Bar b1 = f1.getBar();
          Bar b2 = f2.getBar();
          return Integer.compare(b1.getVal(),b2.getVal());
};

list.sort(comp1);