How to use the Comparator interface, combined with the new features of java8 and source code analysis

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.
Insert image description here

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, comparingthe 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 Comparatorthe 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 Comparatorthrough 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> keyExtractormeans inputting an object of type T and outputting an object of type U. For example, inputting an Peopleobject returns its age Integer value:

//   people -> people.getAge(); 转换为下面方法引用
Function<People, Integer> getAge = People::getAge;

The second parameter keyComparatoris easy to understand and represents the comparison rule used.

Yes c1, features are extracted c2according 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.1keyExtractorkeyComparator(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.Comparatorin the class. It is a static method used to create a comparator ( ) that compares Comparatorobjects based on a given function ( Function) and key comparator ( ) Comparator.

Now let me explain this source code step by step:

  1. <T, U>: This is the definition of a generic method, which indicates that this method has two generic type parameters T, and U, which are used to represent the element type to be compared and the type to extract the key value.

  2. comparingThe method accepts two parameters:

    • keyExtractorThe parameter is a function that accepts Tan object of type and returns Ua key value of type . This function is used to extract the key value of the comparison from the object.
    • keyComparatorThe parameter is a comparator that compares Ukey values ​​of type .
  3. Objects.requireNonNull(keyExtractor)and : These two lines of code are used to check whether Objects.requireNonNull(keyComparator)the incoming keyExtractorand parameters are , and if so , throw .keyComparatornullnullNullPointerException

  4. Return value: The return value of this method is a comparator ( Comparator<T>). This comparator performs object comparison based on keyExtractorthe function and keyComparatorcomparator 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 objects c1and c2, first uses keyExtractorthe function to extract the key values ​​​​from these two objects, and then uses the function keyComparatorto 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 to Comparator<T>type and is marked Serializableso 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 TRepresents the lower bound of wildcard (Lower Bounded Wildcard), which means that objects of supertypes of type Tor can be accepted. TThis allows passing Tmore general object types as arguments. For example, if Tis Number, then ? super Tcan accept Number, Objector other Numbersupertypes of as arguments.

  • ? extends URepresents the upper bound of the wildcard (Upper Bounded Wildcard), which means that objects of type subtypes of Uor can be accepted. UThis allows passing Umore specific object types as arguments than For example, if Uis Integer, then ? extends Ucan accept Integer, Numberor other Integersubtypes of as arguments.

In comparingthe source code of the method, the types of keyExtractorand keyComparatoruse wildcards in order to increase the flexibility of the method. Their type parameters are respectively <? super T, ? extends U>, where Trepresents the element type of the comparator and Urepresents the key value type to be compared. Using wildcards allows comparingmethods 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:

  1. We create a Personclass that represents a person, including name and age attributes.

  2. In mainthe method, we create a Personlist containing objects people.

  3. Use Comparator.comparingthe method to create a comparator ageComparatorthat Personcompares objects by their age. Here person -> person.getAge()is a key extraction function used to extract age as the key value for comparison.

  4. We use sortedthe method to sort peoplethe list according to ageComparatorand get the sorted list sortedPeople.

  5. Finally, we iterate through sortedPeoplethe list and output the sorted results, sorted by age in ascending order.

This example demonstrates how to use comparingthe method to create a comparator and sort objects based on key values. Wildcards <? super T, ? extends U>allow comparingmethods 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 Tand U.
  • UMust be a type that implements Comparablethe interface, but can be Comparable 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 Tand U, and Uqualifies the generic type.

Let me explain step by step what this code snippet means:

  1. <T, U>: This part indicates the declaration of two generic type parameters , Tand U, which are type placeholders used in methods or classes. TTypically represents one type of object, while Urepresents another type and is used as a key in a comparison.

  2. U extends Comparable<? super U>: This part Uqualifies generic types. It means that Uit must be a type that implements Comparablethe interface. ComparableThe interface is an interface used to compare objects in Java. It defines compareTomethods that allow objects to customize comparison logic.

    • Comparable<U>means Uthat must directly implement Comparablethe interface, but we use <? super U>to define it more flexibly U. <? super U>Represents Ucan be Comparableany supertype of interface. This allows us to compare an object Uagainst 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 Tand U.
  • UMust be a type that implements Comparablethe interface, but can be Comparableany 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 Ucan be a class or interface that implements Comparablethe 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 Comparableimplements 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 Fruitclass that implements Comparablethe interface, and then there is a Appleclass that is Fruita subclass of . If we have a method that accepts an Comparableobject 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, Uthe representation can be Comparableany supertype of the interface, so it can accept Fruita or Appleobject as parameter, since they both implement Comparablethe interface . This way, methods become more generic and can accept objects of different types, as long as they implement Comparablethe 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 Comparableto 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, compareIntegersthe method can only compare integer types and return an integer result.

ComparableExample 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 compareObjectsthat accepts two generic parameters T, which must be types that implement Comparablethe interface, and we use extends Comparable<? super T>to indicate that the generic type Tor its supertype must implement Comparablethe interface.

This enables compareObjectsthe method to compare objects of different types, not just integers. We can use it to compare integers and strings, and it will compareToperform 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:

  1. 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 array T[]and returns the largest element in the array. TIs a type parameter, which can be any reference type, such as integer, string, custom object, etc.

  2. 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 is Tan object of type obj1and the other is Uan object of type obj2. This method compares the two objects for equality.

  3. 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. Tis a type parameter that represents the type of value stored in the box. This class can be used to store different types of values.

  4. 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. TIs a type parameter indicating the type of elements in the list. Concrete list implementations can specify Tconcrete 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());

Guess you like

Origin blog.csdn.net/qq_41398619/article/details/132991549