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