Comparator.comparing 对于 Comparator 有什么用
What is the use of Comparator.comparing in respect to Comparator
据我了解,Comparator 是一个函数式接口,用于将 2 个对象与 int compare(T o1, T o2)
进行比较,作为接受两个参数的抽象函数。
但是还有一个函数 Comparator.comparing(s->s)
可以采用只有一个输入参数的 lambda 函数。
例如使用 streams
对 Collection 进行排序
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 表达式转换为双参数 1 还是它还有其他用途.
也请解释下面函数声明的部分。
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
Comparator#compare(T o1, T o2)
比较两个对象和 return 一个基于此标准的整数值:
- 如果 o1 < o2 则为负值
- 正值如果 o1 > o2
- 如果它们相等则为零。
Comparator.comparing(Function<? super T, ? extends U> key)
returns 是一个按该排序键进行比较的比较器。
主要区别在于 compare
方法提供单点比较,而 comparing
链接到其他函数以提供多点比较。
假设你有一个 class Person
public class Person implements Comparable {
private String firstName;
private String lastName;
private int age;
// rest of class omitted
}
如果您使用 compare(p1, p2)
比较两个 Person
实例 p1
与 p2
,将执行比较并且将根据一些自然顺序对两个对象进行排序class规定的。相反,如果您想使用 comparing()
比较相同的两个实例,则将根据您选择的任何标准执行比较,该标准基于 class 的某些属性。例如:Comparator.comparing(Person::getFirstName)
.
因为 comparing
return 是一个 Comparator
而不是一个值,正如我之前所说,您可以链接多个比较。例如:Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName);
至于return类型<T, U extends Comparable<? super U>> Comparator<T>
的含义,可以找.
的解释
我想补充一点,classes 必须具有可比性才能使 compare(T o1, T o2)
起作用。字符串对象是可比较的,因为它们实现了这个接口。也就是说,如果 class 不是 Comparable
,您仍然可以使用 comparing
方法,因为正如我所说,您可以选择 class 的哪个属性用于比较并且这些属性可能是可比较的(即在上面的示例中,在人名或年龄的情况下为字符串)。
is Comparator.comparing()
used for converting a single argument lambda expression to a double argument?
是的,你可以这么想。
排序时,您应该指定“给定两个事物 a
和 b
,它们中哪个更大,或者它们相等?”使用 Comparator<T>
。 a
和 b
是它有 2 个 lambda 参数的原因,而你 return 一个表示你对该问题的答案的整数。
然而,一个更方便的方法是指定“给定一个东西 x
,你想 x
的哪一部分排序 by?”。这就是您可以使用 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
后者显然更加简洁直观。我们倾向于考虑什么的东西来排序,而不是如何比较两个东西,确切的数字要return取决于比较的结果。
关于comparing
的声明:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
<T, U extends Comparable<? super U>>
部分首先声明了两个泛型类型参数——T
是比较器比较的(Person
在上面的例子中),U
是类型你实际上是在比较(在上面的例子中是 String
),因此它是 extends Comparable
.
keyExtractor
是你传入的参数,比如x -> x.getName()
,应该回答“当给定一个T
,什么是U
这个问题你想比较吗?"。
如果您对 ? super
和 ? extends
感到困惑,请阅读 What is PECS?。
如果你还没有意识到,comparing
的实现基本上可以归结为:
return (a, b) -> keyExtractor.apply(a).compareTo(keyExtractor.apply(b));
假设您有自己的自定义对象:
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
,它实现了 comparable.
TL;DR
提供了一个 keyExtrator
来比较对象的 object
或任何 field
,只要 field
也实现了 Comparable
。 returned 是一个使用 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
允许对象通过实现 int compareTo(ob)
的 Comparable interface
方法来提供自然排序。所以一个对象可以将自己与它自己的另一个实例进行比较class(或者可能是祖先的class)。
Comparator
是一种允许比较未实现 Comparable interface
的相关对象的方法。它由 compare(ob1, ob2)
.
调用
上面显示的接口允许 Comparator
被 returned,它利用比较对象的 Comparable
实现。但它也允许通过 keyExtractor
获取该对象的一部分(例如字段)。然后提取的 key
的比较器也必须实现 Comparable
是 returned.
什么说这些后续字段也必须实现 Comparable
?看签名。 return 类型,如果 keyExtractor
是 U
和 U 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, Whosebug.Bar@681a9515, 11
2, Whosebug.Bar@3af49f1c, AA
3, Whosebug.Bar@19469ea2, BA
4, Whosebug.Bar@13221655, CC
5, Whosebug.Bar@2f2c9b19, 2A
6, Whosebug.Bar@31befd9f, AA11
7, Whosebug.Bar@1c20c684, 11AA
8, Whosebug.Bar@1fb3ebeb, AAG
按字符串排序 svalue
Comparator<FooBar> comp = Comparator.comparing(FooBar::getSValue);
list.sort(comp); // sort on svalue
打印
1, Whosebug.Bar@33c7353a, 11
7, Whosebug.Bar@681a9515, 11AA
5, Whosebug.Bar@3af49f1c, 2A
2, Whosebug.Bar@19469ea2, AA
6, Whosebug.Bar@13221655, AA11
8, Whosebug.Bar@2f2c9b19, AAG
3, Whosebug.Bar@31befd9f, BA
4, Whosebug.Bar@1c20c684, CC
按对象栏排序
Comparator<FooBar> comp = Comparator.comparing(FooBar::getBar); // oops!
这行不通。甚至不能在这里定义比较器,因为 Bar
没有按照签名的要求实现 Comparable
。为什么 svalue
被允许?因为它是一个 String
而字符串 class 实现 Comparable
但并非一无所有。以下可以使用 Integer.compare
完成,因为 Bar's
值是一个整数。
Comparator<FooBar> comp1 = (f1,f2)-> {
Bar b1 = f1.getBar();
Bar b2 = f2.getBar();
return Integer.compare(b1.getVal(),b2.getVal());
};
list.sort(comp1);
据我了解,Comparator 是一个函数式接口,用于将 2 个对象与 int compare(T o1, T o2)
进行比较,作为接受两个参数的抽象函数。
但是还有一个函数 Comparator.comparing(s->s)
可以采用只有一个输入参数的 lambda 函数。
例如使用 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));
sorted 方法将 Comparator 作为参数。所以我能够理解第一个 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)
Comparator#compare(T o1, T o2)
比较两个对象和 return 一个基于此标准的整数值:
- 如果 o1 < o2 则为负值
- 正值如果 o1 > o2
- 如果它们相等则为零。
Comparator.comparing(Function<? super T, ? extends U> key)
returns 是一个按该排序键进行比较的比较器。
主要区别在于 compare
方法提供单点比较,而 comparing
链接到其他函数以提供多点比较。
假设你有一个 class Person
public class Person implements Comparable {
private String firstName;
private String lastName;
private int age;
// rest of class omitted
}
如果您使用 compare(p1, p2)
比较两个 Person
实例 p1
与 p2
,将执行比较并且将根据一些自然顺序对两个对象进行排序class规定的。相反,如果您想使用 comparing()
比较相同的两个实例,则将根据您选择的任何标准执行比较,该标准基于 class 的某些属性。例如:Comparator.comparing(Person::getFirstName)
.
因为 comparing
return 是一个 Comparator
而不是一个值,正如我之前所说,您可以链接多个比较。例如:Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName);
至于return类型<T, U extends Comparable<? super U>> Comparator<T>
的含义,可以找
我想补充一点,classes 必须具有可比性才能使 compare(T o1, T o2)
起作用。字符串对象是可比较的,因为它们实现了这个接口。也就是说,如果 class 不是 Comparable
,您仍然可以使用 comparing
方法,因为正如我所说,您可以选择 class 的哪个属性用于比较并且这些属性可能是可比较的(即在上面的示例中,在人名或年龄的情况下为字符串)。
is
Comparator.comparing()
used for converting a single argument lambda expression to a double argument?
是的,你可以这么想。
排序时,您应该指定“给定两个事物 a
和 b
,它们中哪个更大,或者它们相等?”使用 Comparator<T>
。 a
和 b
是它有 2 个 lambda 参数的原因,而你 return 一个表示你对该问题的答案的整数。
然而,一个更方便的方法是指定“给定一个东西 x
,你想 x
的哪一部分排序 by?”。这就是您可以使用 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
后者显然更加简洁直观。我们倾向于考虑什么的东西来排序,而不是如何比较两个东西,确切的数字要return取决于比较的结果。
关于comparing
的声明:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
<T, U extends Comparable<? super U>>
部分首先声明了两个泛型类型参数——T
是比较器比较的(Person
在上面的例子中),U
是类型你实际上是在比较(在上面的例子中是 String
),因此它是 extends Comparable
.
keyExtractor
是你传入的参数,比如x -> x.getName()
,应该回答“当给定一个T
,什么是U
这个问题你想比较吗?"。
如果您对 ? super
和 ? extends
感到困惑,请阅读 What is PECS?。
如果你还没有意识到,comparing
的实现基本上可以归结为:
return (a, b) -> keyExtractor.apply(a).compareTo(keyExtractor.apply(b));
假设您有自己的自定义对象:
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
,它实现了 comparable.
TL;DR
提供了一个 keyExtrator
来比较对象的 object
或任何 field
,只要 field
也实现了 Comparable
。 returned 是一个使用 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
允许对象通过实现 int compareTo(ob)
的 Comparable interface
方法来提供自然排序。所以一个对象可以将自己与它自己的另一个实例进行比较class(或者可能是祖先的class)。
Comparator
是一种允许比较未实现 Comparable interface
的相关对象的方法。它由 compare(ob1, ob2)
.
上面显示的接口允许 Comparator
被 returned,它利用比较对象的 Comparable
实现。但它也允许通过 keyExtractor
获取该对象的一部分(例如字段)。然后提取的 key
的比较器也必须实现 Comparable
是 returned.
什么说这些后续字段也必须实现 Comparable
?看签名。 return 类型,如果 keyExtractor
是 U
和 U 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, Whosebug.Bar@681a9515, 11
2, Whosebug.Bar@3af49f1c, AA
3, Whosebug.Bar@19469ea2, BA
4, Whosebug.Bar@13221655, CC
5, Whosebug.Bar@2f2c9b19, 2A
6, Whosebug.Bar@31befd9f, AA11
7, Whosebug.Bar@1c20c684, 11AA
8, Whosebug.Bar@1fb3ebeb, AAG
按字符串排序 svalue
Comparator<FooBar> comp = Comparator.comparing(FooBar::getSValue);
list.sort(comp); // sort on svalue
打印
1, Whosebug.Bar@33c7353a, 11
7, Whosebug.Bar@681a9515, 11AA
5, Whosebug.Bar@3af49f1c, 2A
2, Whosebug.Bar@19469ea2, AA
6, Whosebug.Bar@13221655, AA11
8, Whosebug.Bar@2f2c9b19, AAG
3, Whosebug.Bar@31befd9f, BA
4, Whosebug.Bar@1c20c684, CC
按对象栏排序
Comparator<FooBar> comp = Comparator.comparing(FooBar::getBar); // oops!
这行不通。甚至不能在这里定义比较器,因为 Bar
没有按照签名的要求实现 Comparable
。为什么 svalue
被允许?因为它是一个 String
而字符串 class 实现 Comparable
但并非一无所有。以下可以使用 Integer.compare
完成,因为 Bar's
值是一个整数。
Comparator<FooBar> comp1 = (f1,f2)-> {
Bar b1 = f1.getBar();
Bar b2 = f2.getBar();
return Integer.compare(b1.getVal(),b2.getVal());
};
list.sort(comp1);