一文掌握Comparator的數十種用法
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,這個前面也講過
但是有時候你會看到類似下面的程式碼缺可以執行,這是為什麼呢,這是因為, 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);
}