Comparator.comparing 对于 Comparator 有什么用

发布于 2025-01-15 13:33:56 字数 840 浏览 2 评论 0 原文

据我了解 Comparator 是一个函数接口,用于比较 2 个对象,其中 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));

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

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

As far as I understand Comparator is a functional interface used to compare 2 objects with int compare(T o1, T o2) as the abstract function that takes two argument.
but there is also a function Comparator.comparing(s->s) that can take a lambda function with only one input parameter.
for Example to sort a Collection using streams

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

the sorted method takes a Comparator as a argument. So I am able to understand the first lambda expression but I wonder the use of Comparator.comparing(s->s) i.e. is Comparator.comparing() used for converting a single argument lambda expression to a double argument one or does it has some other use as well.
Also please explain the part of the below function declaration.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

书信已泛黄 2025-01-22 13:33:56

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

是的,你可以这样想。

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

然而,更方便的方法是指定“给定一个事物 x,您想要按 x 的哪一部分排序 ?”。这就是您可以使用 Comparator.comparing 的 keyExtractor 参数执行的操作。

比较:

/*
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<Person> personNameComparator = 
    (a, b) -> a.getName().compareTo(b.getName());

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

后者显然更加简洁直观。我们倾向于考虑按什么事物进行排序,而不是如何准确地比较两个事物,以及根据比较结果返回的确切数字。

至于比较的声明:

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

> 部分首先声明两个泛型类型参数 - T 是比较器比较的内容(在上述情况下为 Person),而 U 是您实际比较的类型(在上述情况下是String),因此它扩展了 Comparable

keyExtractor 是你传入的参数,如x -> x.getName(),这应该回答“当给定一个 T 时,您想要比较的 U 是什么?”的问题。

如果您对感到困惑?超级和<代码>?扩展,阅读什么是PECS?

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

return (a, b) -> keyExtractor.apply(a).compareTo(keyExtractor.apply(b));

is Comparator.comparing() used for converting a single argument lambda expression to a double argument?

Yes, you can sort of think of it like that.

When sorting things, you are supposed to specify "given two things a and b, which of them is greater, or are they equal?" using a Comparator<T>. The a and b is why it has 2 lambda parameters, and you return an integer indicating your answer to that question.

However, a much more convenient way to do this is to specify "given a thing x, what part of x do you want to sort by?". And that is what you can do with the keyExtractor argument of Comparator.comparing.

Compare:

/*
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<Person> personNameComparator = 
    (a, b) -> a.getName().compareTo(b.getName());

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

The latter is clearly much more concise and intuitive. We tend to think about what things to sort by, rather than how exactly to compare two things, and the exact number to return depending on the comparison result.

As for the declaration for comparing:

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

The <T, U extends Comparable<? super U>> part first declares two generic type parameters - T is what the comparator compares (Person in the above case), and U is the type that you are actually comparing (String in the above case), hence it extends Comparable.

keyExtractor is the parameter you pass in, such as x -> x.getName(), that should answer the question of "when given a T, what is a U that you want to compare by?".

If you are confused by the ? super and ? extends, read What is PECS?.

If you haven't realised already, the implementation of comparing basically boils down to:

return (a, b) -> keyExtractor.apply(a).compareTo(keyExtractor.apply(b));
哥,最终变帅啦 2025-01-22 13:33:56

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

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

Comparator.comparing(Function key) 返回按该排序键进行比较的 Comparator

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

假设您有一个类 Person

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

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

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

至于返回类型的含义>比较器,您可以在此处找到说明。

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

Comparator#compare(T o1, T o2) Compare two objects and returns an integer value based on this criteria:

  • A negative value if o1 < o2
  • A positive value if o1 > o2
  • Zero if they are equal.

Comparator.comparing(Function<? super T, ? extends U> key) returns a Comparator<T> that compares by that sort key.

The main difference is that compare method provides a single point of comparison, whereas comparing chained to other functions to provide multiple points of comparison.

Suppose you have a class Person

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

if you compare two Person instances p1 vs p2 using compare(p1, p2), the comparison will be executed and the two objects will be sorted based on some natural ordering prescribed by the class. In contrast, if you want to compare the same two instances using comparing(), the comparison will be executed based on whichever criteria you choose to compare based on some attribute of the class. For example: Comparator.comparing(Person::getFirstName).

Because comparing returns a Comparator rather than a value, as I stated before, you can chain multiple comparisons. For instance: Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName);

As for the meaning of the return type <T, U extends Comparable<? super U>> Comparator<T>, you can find the explanation here.

I want to add that, classes must be comparable in order for compare(T o1, T o2) to work. String objects are comparable because they implement this interface. That said, if a class is not Comparable, you can still use comparing method because as I stated, you get to choose which attribute of the class you would like to use for the comparison and those attributes are likely to be comparable (i.e. String in the case of person's name or age in the above example).

樱娆 2025-01-22 13:33:56

想象一下您有自定义对象:

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 keyExtractor)。它看起来像这样:

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

或者使用方法引用更好:

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

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

请注意,像这样提取的密钥必须可比较。在上面的例子中,提取的键是String,它实现了comparable。

Imagine you have your custom object:

public class Person {

  private String firstName;
  private String lastName;

  //getters and setters
}

Let's now say you have to compare people by their first names. One option is to write comparator like this:

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

This should be obvious - get first name of one person, and compare it to the first name of other person. Other option is to write this same comparator using Comparator.comparing(Function<? super T,? extends U> keyExtractor). It will look like this:

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

Or even better with method reference:

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

The single argument you provide here is a Function, it accepts a single parameter, this is why your lambda also uses a single parameter. The function here works as a key extractor - it extracts from your object the keys to be compared. It's like saying, get first name from first object and compare it to first name of second object.

Just note, that keys extracted like this must be Comparable. In the example above extracted key is String, which implements comparable.

梦晓ヶ微光ヅ倾城 2025-01-22 13:33:56

TL;DR

提供keyExtrator来比较对象或对象的任何字段,只要< code>field 还实现了 Comparable。返回的是一个使用 fields compareTo 方法的 Comparator

故事的其余部分

这是来自 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 允许对象通过实现 < intcompareTo(ob) 的 code>Comparable 接口 方法。因此,一个对象可以将自己与它自己的类(或者可能是祖先类)的另一个实例进行比较。

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

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

什么说这些后续字段也必须实现 Comparable?看签名。如果 keyExtractorUU extends Comparable< 时的返回类型?超级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

在对象 Bar 上排序

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

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

但一切并没有丢失。由于 Bar 的 值是一个 int,因此可以使用 Integer.compare 完成以下操作。

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

list.sort(comp1);

TL;DR

A keyExtrator is provided to compare the object or any field of the object as long as the field also implements Comparable. What is returned is a Comparator that uses the fields compareTo method.

The rest of the story

Here is the complete method from the Comparator interface

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

First, Comparable allows an object to provide a natural ordering by implementing the Comparable interface method of int compareTo(ob). So an object can compare itself to another instance of its own class (or perhaps ancestral class).

Comparator is a way to allow comparisons of related objects that don't implement the Comparable interface. It is called by compare(ob1, ob2).

The interface shown above allows a Comparator to be returned that makes use of the Object under comparison's Comparable implementation. But it also allows for a part of that object (e.g. a field) to be obtained via a keyExtractor. Then the Comparator for the extracted key which must also implement Comparable is returned.

What says that this these subsequent fields must also implement Comparable? Look at the signature. The return type if the keyExtractor is U and U extends Comparable<? super U>.

Here are some examples with explanations.

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

Natural ordering sort of FooBar

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

prints

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

Sort on String svalue

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

prints

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

Sort on object Bar

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

This won't work. Can't even define a comparator here because Bar does not implement Comparable as required by the signature. Why was svalue allowed? Because it is a String and the String class implements Comparable

But all is not lost. The following could be done using Integer.compare since Bar's value is an int.

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

list.sort(comp1);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文