1. 程式人生 > 其它 >一文掌握Comparator的數十種用法

一文掌握Comparator的數十種用法

技術標籤:javajava

Comparator

為什麼有的時候可以直接呼叫 Collections.sort 方法

我們知道,當你不提供Comparator 作為引數的時候,這個時候我們要求被排序的物件要實現Comparable 介面,這個我們已經學習了很多了,例如

class People  {
    public String name;
    public int age;

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } @Test public void comparatorUsage() { List<People> peopleList = new ArrayList<>(); peopleList.
add(new People("b", 2)); peopleList.add(new People("a", 1)); Collections.sort(peopleList); }

例如當上面的程式碼中,People類沒有實現Comparable 介面的時候,你就會看到下面的錯誤,這個時候你只要讓People實現Comparable 介面即可,如果People不是你寫的程式碼或者你不想實現Comparable ,這個時候你就可以使用Comparator,這個前面也講過

image-20201209182548868

但是有時候你會看到類似下面的程式碼缺可以執行,這是為什麼呢,這是因為, String 類本身已經實現了 Comparable介面,所以我們在這裡就可以得到一個結論,那就是如果不提供Comparator ,那麼被排序的物件必須實現Comparable介面

static List<String> testList = null;

@BeforeAll
public static void setUp() {
    testList = new ArrayList<>();
    testList.add("d");
    testList.add("a");
    testList.add("c");
    testList.add("b");
    System.out.println("====================Original List========================");
    testList.forEach(System.out::println);
}

@Test
public void defaultSort() {
    Collections.sort(testList);
    System.out.println("====================Default List========================");
    testList.forEach(System.out::println);
}

Comparator.naturalOrder 和 Comparator.reverseOrder

很多時候我們會面臨這樣的場景,那就是排序邏輯不變,一會兒根據升序排序,一會根據降序排序,這個時候如果我們的Comparable 中的排序邏輯可以滿足上面的排序,就是排序型別(升序還是降序)是不滿足的,這個時候我們就可以配合Comparator,來改變原來預設的排序型別(其實就是升序)

nullsFirst 和 nullsLast

首先我們看一下引入這兩個方法的原因是什麼

public class JavaComparators {
    static List<String> testList = null;

    @BeforeAll
    public static void setUp() {
        testList = new ArrayList<>();
        testList.add("d");
        testList.add("a");
        testList.add("c");
        testList.add("b");
        System.out.println("====================Original List========================");
        testList.forEach(System.out::println);
    }
    @Test
    public void defaultSort() {
        testList.add(null);
        Collections.sort(testList);
        System.out.println("====================Default List========================");
        testList.forEach(System.out::println);
    }
}

接下來,你就會收到如下錯誤,那就是NullPointerException,這是應為我們對在排序的過程中,呼叫物件的方法或者屬性。這個時候就會丟擲空指標異常

====================Original List========================
d
a
c
b
java.lang.NullPointerException
	at java.util.ComparableTimSort.binarySort(ComparableTimSort.java:262)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:189)
	at java.util.Arrays.sort(Arrays.java:1312)

這個時候我們可以根據業務的特點,選擇nullsFirst 還是nullsLast

    @Test
    public void nullsFirst(){
        testList.add(null);
        System.out.println("============== nullsFirst ==============");
        Collections.sort(testList,Comparator.nullsFirst(Comparator.naturalOrder()));
        testList.forEach(System.out::println);
        System.out.println("============== nullsLast ==============");
        Collections.sort(testList,Comparator.nullsLast(Comparator.naturalOrder()));
        testList.forEach(System.out::println);
    }

輸出結果如下,因為這裡我們選擇則是自然排序,可以看出在原來排序的基礎上,將null 值放到了合適的地方,這裡需要注意的是Comparator.nullsLast和Comparator.nullsFirst方法依然需要Comparator作為引數,這個時候我們就可以將我們在前面學習的兩種Comparator中的一個作為引數傳遞給它

====================Original List========================
d
a
c
b

============== nullsFirst ==============
null
a
b
c
d
============== nullsLast ==============
a
b
c
d
null

comparing

Comparator.comparing(keyExtractor,keyComparator)接受兩個引數

第一個是keyExtractor,你可以把它認為是兩個需要比較物件的比較部分,說白了就是提取出需要比較的部分,在這裡我需要比較的是名字和年齡拼接起來的一個欄位

第二個是Comparator,則是你需要定義比較的邏輯,本來Comparator中compare的引數直接是需要比較的兩個物件,這裡則是keyExtractor的輸出

@Test
public void comparing() {
    List<People> peopleList = new ArrayList<>();
    peopleList.add(new People("b", 2));
    peopleList.add(new People("a", 1));

    Collections.sort(peopleList, Comparator.comparing(
            (people) -> people.getName() + people.getAge(),
            (o1, o2) -> o1.compareTo(o2)));
    System.out.println("====================Reverse List========================");
    peopleList.forEach(System.out::println);
}

輸出結果

====================Reverse List========================
Student{name='a', age=1}
Student{name='b', age=2}

當然comparing 方法還有另外一種形式,那就是隻提供keyExtractor,因為這個時候它會給你提供一個預設的Comparator

就是keyExtractor提取出來的key,然後對key 呼叫進行compareTo 方法作為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));
}

但是這裡有個注意的地方時,你提取出來的key必須是實現Comparable 介面的,因為它接下來要呼叫key 的compareTo 方法

@Test
public void comparing2() {
    List<People> peopleList = new ArrayList<>();
    peopleList.add(new People("b", 2));
    peopleList.add(new People("a", 1));

    Collections.sort(peopleList, Comparator.comparing(
            (people) -> people.getName() + people.getAge()));
    System.out.println("====================Reverse List========================");
    peopleList.forEach(System.out::println);

}

輸出結果

====================Reverse List========================
Student{name='a', age=1}
Student{name='b', age=2}

Comparator的正常實現

class MyComparator implements Comparator<People> {
    @Override
    public int compare(People o1, People o2) {
        return (this.name + this.age).compareTo(o.name + age);
    }
}

然後在需要使用的地方,直接 new MyComparator 即可

Comparator的匿名內部類的實現

@Test
public void comparatorUsageByAnonymous() {
    List<People> peopleList = new ArrayList<>();
    peopleList.add(new People("b", 2));
    peopleList.add(new People("a", 1));

    Collections.sort(peopleList, new Comparator<People>() {
        @Override
        public int compare(People o1, People o2) {
            return (o1.name + o1.age).compareTo(o2.name + o2.age);
        }
    });
    System.out.println("====================Reverse List========================");
    peopleList.forEach(System.out::println);
}

上面的這個實現和People實現Comparable的下面這種方式是等價的

@Override
public int compareTo(Student o) {
    return (this.name + this.age).compareTo(o.name + age);
}

需要注意的是,匿名內部類往往是在這個Comparator只使用一次的時候才使用,所以如果你要多次使用還是要以類的方式將其提取出來

Comparator的Lambda 表示式的實現

public void comparatorUsageByLambda() {
    List<People> peopleList = new ArrayList<>();
    peopleList.add(new People("b", 2));
    peopleList.add(new People("a", 1));
    
    Collections.sort(peopleList, (o1, o2) -> (o1.name + o1.age).compareTo(o2.name + o2.age));
    System.out.println("====================Reverse List========================");
    peopleList.forEach(System.out::println);
}