Table of contents
- 1 Introduction to Comparator
- 2. Methods in Comparator
1 Introduction to Comparator
It is recommended to read the English documentation
Official English document introduction
https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/Comparator.html
Comparator is a functional interface and therefore can be used as the assignment target of a lambda expression or method reference. It is often used to sort collections that do not have a natural sort, such as Collections.sort or Arrays.sort. Or declare the sorting rules of some ordered data structures, such as TreeSet and TreeMap. That is to say, this interface is mainly used for collection sorting.
1.1 Functional declaration
Functional Declaration refers to declaring functions or methods using functional programming in Java. Functional programming is a programming paradigm that treats computation as a way of applying functions, emphasizing the purity and immutability of functions. In Java, functional programming is usually implemented using features such as Lambda expressions, method references, and functional interfaces. In the code you provided, the Lambda expression (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2))
is a functional declaration, which represents the definition of a comparator and abstracts the comparison rules of objects into a function.
In summary, comparing
the method is an example of functional programming that accepts a function and a comparator as parameters and returns a comparator based on the extracted key value and key comparator for sorting the object. This approach allows you to easily compare objects based on their properties without having to write a lot of comparison logic.
1.2 Simple small case
List<People> peoples = new ArrayList<>();
// 中间省略
// 按照年龄从小到大排序
peoples.sort(Comparator.comparing(People::getAge));
2. Methods in Comparator
As a functional interface, Comparator has only one abstract method, but it has many default methods. Let's get to know these methods.
2.1 compare abstract method
As Comparator
the only abstract method, int compare(T o1,T o2)
it compares the sizes of two parameters and returns negative integers, zero, and positive integers, which represent o1<o2, o1=o2, and o1>o2 respectively. Usually, -1, 0, or 1 are returned respectively. Pseudo expression:
// 输入两个同类型的对象 ,输出一个比较结果的int数字
(x1,x2)-> int
example
Do it yourself
public class SortedDemo {
public static void main(String[] args) {
List<String> words = Arrays.asList("watermelon","apple", "banana", "cherry");
// 使用比较器按字符串长度升序排序
words.sort((str2, str1) -> Integer.compare(str1.length(), str2.length()));
// words.sort((str2, str1) -> Integer.compare(str1.length(), str2.length()));//降序
System.out.println(words); // 输出: [apple, banana, cherry]
}
}
2.2 comparing method
Starting from Java 8 , a series of static methods are provided and given more powerful and convenient functions Comparator
through a functional style . Let's temporarily call them series methods.Comparator
comparing
Let’s focus on
the comparing method and its source code implementation. Note that the comparing method has many different overloaded methods. This is the most comprehensive introduction to the method of passing in parameters.
Source code
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
Reference explanation
This method is the basic method of this series of methods. Doesn’t it look difficult to understand? Let's analyze this method. Both of its parameters are functional interfaces.
The first parameter Function<? super T, ? extends U> keyExtractor
means inputting an object of type T and outputting an object of type U. For example, inputting an People
object returns its age Intege
r value:
// people -> people.getAge(); 转换为下面方法引用
Function<People, Integer> getAge = People::getAge;
The second parameter keyComparator
is easy to understand and represents the comparison rule used.
Yes c1
, features are extracted c2
according to the rules provided by the first parameter , and then the second parameter compares the two features. The following formula can actually be summarized as 3.1keyExtractor
keyComparator
(x1,x2)-> int
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2))
Comparator & Serializable is a new feature of Java 8: satisfying these two type constraints at the same time
After understanding this method, other methods in this series will be easy to understand, so I won’t go into details here. At present, the comparing series of methods are more widely used. Let's give some examples:
List<People> peoples = new ArrayList<>();
// ………………
// 按照年龄从低到高排序
peoples.sort(Comparator.comparing(People::getAge));
// 按照年龄从高到低排序
peoples.sort(Comparator.comparing(People::getAge, (x, y) -> -x.compareTo(y)));
Likewise, you can use Comparator using the sorting methods provided by java.util.Collections or Stream.
Detailed explanation
This source code is a method in the Java standard library, located java.util.Comparator
in the class. It is a static method used to create a comparator ( ) that compares Comparator
objects based on a given function ( Function
) and key comparator ( ) Comparator
.
Now let me explain this source code step by step:
-
<T, U>
: This is the definition of a generic method, which indicates that this method has two generic type parametersT
, andU
, which are used to represent the element type to be compared and the type to extract the key value. -
comparing
The method accepts two parameters:keyExtractor
The parameter is a function that acceptsT
an object of type and returnsU
a key value of type . This function is used to extract the key value of the comparison from the object.keyComparator
The parameter is a comparator that comparesU
key values of type .
-
Objects.requireNonNull(keyExtractor)
and : These two lines of code are used to check whetherObjects.requireNonNull(keyComparator)
the incomingkeyExtractor
and parameters are , and if so , throw .keyComparator
null
null
NullPointerException
-
Return value: The return value of this method is a comparator (
Comparator<T>
). This comparator performs object comparison based onkeyExtractor
the function andkeyComparator
comparator passed in.(Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2))
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2))
: This is a Lambda expression that represents the specific implementation of a comparator. It accepts two objectsc1
andc2
, first useskeyExtractor
the function to extract the key values from these two objects, and then uses the functionkeyComparator
to compare the two key values and return the comparison result. This implements a key-value based object comparator.(Comparator<T> & Serializable)
: This is a type conversion that converts the Lambda expression toComparator<T>
type and is markedSerializable
so that the comparator can be serialized.
The main purpose of this method is to create a comparator that can sort objects based on extracted key values and key comparators. This approach makes sorting more flexible and can be compared based on a certain attribute of the object instead of directly comparing the objects themselves.
<? super T, ? extends U>Explanation
<? super T, ? extends U>
wildcard
-
? super T
Represents the lower bound of wildcard (Lower Bounded Wildcard), which means that objects of supertypes of typeT
or can be accepted.T
This allows passingT
more general object types as arguments. For example, ifT
isNumber
, then? super T
can acceptNumber
,Object
or otherNumber
supertypes of as arguments. -
? extends U
Represents the upper bound of the wildcard (Upper Bounded Wildcard), which means that objects of type subtypes ofU
or can be accepted.U
This allows passingU
more specific object types as arguments than For example, ifU
isInteger
, then? extends U
can acceptInteger
,Number
or otherInteger
subtypes of as arguments.
In comparing
the source code of the method, the types of keyExtractor
and keyComparator
use wildcards in order to increase the flexibility of the method. Their type parameters are respectively <? super T, ? extends U>
, where T
represents the element type of the comparator and U
represents the key value type to be compared. Using wildcards allows comparing
methods to accept a wider range of type parameters, allowing for greater flexibility in adapting to different use cases.
comparing code sample
comparing
package com.qfedu.Comparator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* public static <T, U> Comparator<T> comparing(
* Function<? super T, ? extends U> keyExtractor,
* Comparator<? super U> keyComparator)
* {
* Objects.requireNonNull(keyExtractor);
* Objects.requireNonNull(keyComparator);
* return (Comparator<T> & Serializable)
* (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
* keyExtractor.apply(c2));
* }
*/
public class ComparatorDemo {
public static void main(String[] args) {
// 创建一个包含Person对象的列表
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
// 使用comparing方法按年龄升序排序Person对象
Comparator<Person> ageComparator = Comparator.comparing(
person -> person.getAge() // 提取比较的键值:年龄
);
// 对列表进行排序
List<Person> sortedPeople = people.stream()
.sorted(ageComparator)
.collect(Collectors.toList());
// 输出排序后的结果
sortedPeople.forEach(person -> System.out.println(person.getName() + ": " + person.getAge()));
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Annotation explanation:
-
We create a
Person
class that represents a person, including name and age attributes. -
In
main
the method, we create aPerson
list containing objectspeople
. -
Use
Comparator.comparing
the method to create a comparatorageComparator
thatPerson
compares objects by their age. Hereperson -> person.getAge()
is a key extraction function used to extract age as the key value for comparison. -
We use
sorted
the method to sortpeople
the list according toageComparator
and get the sorted listsortedPeople
. -
Finally, we iterate through
sortedPeople
the list and output the sorted results, sorted by age in ascending order.
This example demonstrates how to use comparing
the method to create a comparator and sort objects based on key values. Wildcards <? super T, ? extends U>
allow comparing
methods to adapt to different types of objects and keys. In this case, the key is age and the object is Person
.
Method source code analysis in example comparison
The comparing method in the example only passes in one function parameter Function<? super T, ? extends U>
, and the declaration of the method <T, U extends Comparable<? super U>>
indicates:
- A method or class accepts two generic type parameters
T
andU
. U
Must be a type that implementsComparable
the interface, but can beComparable
any supertype of the interface.
// 使用comparing方法按年龄升序排序Person对象
Comparator<Person> ageComparator = Comparator.comparing(
person -> person.getAge() // 提取比较的键值:年龄
);
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));
}
<T, U extends Comparable<? super U>> 讲解
This code snippet <T, U extends Comparable<? super U>>
is a Java generic type parameter declaration, which contains two generic type parameters T
and U
, and U
qualifies the generic type.
Let me explain step by step what this code snippet means:
-
<T, U>
: This part indicates the declaration of two generic type parameters ,T
andU
, which are type placeholders used in methods or classes.T
Typically represents one type of object, whileU
represents another type and is used as a key in a comparison. -
U extends Comparable<? super U>
: This partU
qualifies generic types. It means thatU
it must be a type that implementsComparable
the interface.Comparable
The interface is an interface used to compare objects in Java. It definescompareTo
methods that allow objects to customize comparison logic.Comparable<U>
meansU
that must directly implementComparable
the interface, but we use<? super U>
to define it more flexiblyU
.<? super U>
RepresentsU
can beComparable
any supertype of interface. This allows us to compare an objectU
against its subclass or superclass objects.
Taken together, <T, U extends Comparable<? super U>>
this statement says:
- A method or class accepts two generic type parameters
T
andU
. U
Must be a type that implementsComparable
the interface, but can beComparable
any supertype of the interface .
The purpose of this declaration is to make the method or class more general and flexible, able to adapt to different types of objects, and to perform comparison operations instead of being limited to specific types. This is useful when writing general sorting or comparison logic.
What does supertype mean?
In Java, a class or interface can implement another class or interface, and the implemented class or interface is called a super type. Supertype is a broad concept used to indicate that a class or interface is the parent class or parent interface of another class or interface.
When we say "U can be any supertype of the Comparable interface," we mean that the generic type parameter U
can be a class or interface that implements Comparable
the interface, not just classes that directly implement Comparable
the interface.
This is very useful because it makes the method more flexible, allowing it to accept objects not only from the class that implements the interface, but also from subclasses of the class that Comparable
implements the interface. This is because subclasses can also be considered a type of supertype, since they inherit the behavior of the supertype.Comparable
For example, suppose there is a Fruit
class that implements Comparable
the interface, and then there is a Apple
class that is Fruit
a subclass of . If we have a method that accepts an Comparable
object that implements the interface, the declaration of this method can be written like this:
public <U extends Comparable<? super U>> void someMethod(U obj) {
// 方法体
}
In this method, U
the representation can be Comparable
any supertype of the interface, so it can accept Fruit
a or Apple
object as parameter, since they both implement Comparable
the interface . This way, methods become more generic and can accept objects of different types, as long as they implement Comparable
the interface or its supertype.
Generics enhance flexibility example
I don't quite feel how versatile and flexible it is. Can you use a code example to illustrate it? It would be better to have a comparison.
When we define generic methods or classes, we want them to be able to handle various types of data, not just specific types. By using extends Comparable<? super U>
such generic type parameter qualifications, we can implement a general comparison method that can be applied to different types of objects.
Below, I'll provide you with an example of how to use generics and Comparable
to create a generic comparison method. First, we will create a normal comparison method and then extends Comparable<? super U>
improve it with generics and make it more general.
Example 1: Common comparison methods
public static int compareIntegers(int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}
public static void main(String[] args) {
int result = compareIntegers(5, 3);
System.out.println(result); // 输出 1
}
In this example, compareIntegers
the method can only compare integer types and return an integer result.
Comparable
Example 2: Generic comparison method using generics and
public static <T extends Comparable<? super T>> int compareObjects(T a, T b) {
return a.compareTo(b);
}
public static void main(String[] args) {
int result1 = compareObjects(5, 3); // 使用整数比较
System.out.println(result1); // 输出 1
String str1 = "apple";
String str2 = "banana";
int result2 = compareObjects(str1, str2); // 使用字符串比较
System.out.println(result2); // 输出 -1
}
In Example 2, we define a generic method compareObjects
that accepts two generic parameters T
, which must be types that implement Comparable
the interface, and we use extends Comparable<? super T>
to indicate that the generic type T
or its supertype must implement Comparable
the interface.
This enables compareObjects
the method to compare objects of different types, not just integers. We can use it to compare integers and strings, and it will compareTo
perform comparisons based on the object's methods to produce universal comparison results.
Through generic sum Comparable
, we implement a universal comparison method that can be used to compare objects of different types, which increases the versatility and flexibility of the method. This is a powerful aspect of generics and type parameter qualification.
Method type parameter example
Type parameter declarations are used to specify generic type parameters used in a method or class to make the method or class more general and can adapt to different types of data. Here are some examples demonstrating type parameter declarations for methods:
-
Simple generic method
public <T> T findMax(T[] arr) { T max = arr[0]; for (T element : arr) { if (element.compareTo(max) > 0) { max = element; } } return max; }
In this example,
<T>
it means that this is a generic method that accepts a generic arrayT[]
and returns the largest element in the array.T
Is a type parameter, which can be any reference type, such as integer, string, custom object, etc. -
Use multiple type parameters
public <T, U> boolean areEqual(T obj1, U obj2) { return obj1.equals(obj2); }
There are two type parameters in this example
<T, U>
. The method accepts two parameters, one isT
an object of typeobj1
and the other isU
an object of typeobj2
. This method compares the two objects for equality. -
Type parameters of generic classes
public class Box<T> { private T value; public Box(T value) { this.value = value; } public T getValue() { return value; } }
This is an
Box<T>
example of a generic class.T
is a type parameter that represents the type of value stored in the box. This class can be used to store different types of values. -
Type parameters of generic interfaces
public interface List<T> { void add(T item); T get(int index); }
This is an example of a generic interface
List<T>
, representing a generic list interface.T
Is a type parameter indicating the type of elements in the list. Concrete list implementations can specifyT
concrete types.
These examples show how to use type parameter declarations in methods and classes to achieve generality and flexibility in adapting to different types of data. Type parameters allow us to write generic code without having to write a different method or class every time a different type is used.
sorted source code analysis
Stream<T> sorted(Comparator<? super T> comparator);
The sorted method will use the passed in comparator to sort the elements in the Stream, and then generate a new Stream containing the elements in the order defined by the comparator. This method is usually used to sort elements in a collection
// 使用comparing方法按年龄升序排序Person对象
Comparator<Person> ageComparator = Comparator.comparing(
person -> person.getAge() // 提取比较的键值:年龄
);
//sort(Comparator.comparing(People::getAge)); 也可以这么写
// 对列表进行排序
List<Person> sortedPeople = people.stream()
.sorted(ageComparator)
.collect(Collectors.toList());