這篇“怎么使用Lambda表達式簡化Comparator的使用問題”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么使用Lambda表達式簡化Comparator的使用問題”文章吧。
使用集合時,如果需要實現集合元素排序的話,通常有兩種選擇,元素本身實現 Comparable
接口或者集合使用 Comparator
對象實現排序。這里來介紹一個 Comparator
這個類。
Comparator 是一個函數式接口,這個可以從它的定義上看出來。它具有這個注解:@FunctionalInterface
。
這個注解標注此接口屬于函數式接口,意味著只能有一個抽象方法,但是帶你進去看,你會發現兩個抽象方法!
int compare(T o1, T o2); boolean equals(Object obj);
這并不是定義錯誤,而是上面那個注解(@FunctionalInterface
)的文檔里有說明:如果接口聲明了一個覆蓋了 java.lang.Object 的全局方法之一的抽象方法,那么它不會計入接口的抽象方法數量中,因為接口的任何實現都將具有 java.lang.Object 或者其它地方的實現。 因此,它確實是只有一個抽象方法:
int compare(T o1, T o2);
package com.dragon; public class Dog { private String name; private int age; private double weight; public Dog(String name, int age, double weight) { super(); this.name = name; this.age = age; this.weight = weight; } //省略 getter 和 setter 方法,使用 IDE 自動生成比較方便。 //下面兩個方法,也都可以自動生成。 @Override public String toString() { return "Dog [name=" + name + ", age=" + age + ", weight=" + weight + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); long temp; temp = Double.doubleToLongBits(weight); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Dog other = (Dog) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (Double.doubleToLongBits(weight) != Double.doubleToLongBits(other.weight)) return false; return true; } }
這個接口雖然是一個函數式接口,但是它的方法可不少!所以,它可以實現非常豐富的排序功能!
**排序規則是按照年齡升序。我這里使用的表達式為:
o1.getAge()-o2.getAge();
如果想要實現反序,調換 o1和o2的位置即可,但是我們不使用這種方式。下面會使用更加方便的方式。
**
1.使用原始的匿名內部類方式,實現 Comparator 對象。
package com.dragon; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class ComparatorTest { public static void main(String[] args) { //測試使用的集合,下面不再提供,只提供方法的實現。 List<Dog> dogList = new ArrayList<>(); dogList.add(new Dog("小黑", 3, 37.0)); dogList.add(new Dog("二哈", 2, 40.0)); dogList.add(new Dog("泰迪", 1, 8.0)); dogList.add(new Dog("大黃", 4, 55.0)); rawComparator(dogList); } /** * 原始的實現比較器的方法,使用匿名類 * */ static void rawComparator(List<? extends Dog> dogList) { dogList.sort(new Comparator<Dog>() { @Override public int compare(Dog o1, Dog o2) { return o1.getAge()-o2.getAge(); } }); dogList.forEach(System.out::println); } }
說明:這樣顯得較為繁瑣,不夠體現代碼的簡介,下面使用Java8的 lambda 表達式來改寫。
運行結果:
2.使用Java8 的lambda 表達式來簡化代碼
/** * 使用lambda的寫法 * */ static void lambda(List<? extends Dog> dogList) { Comparator<Dog> c = (dog1, dog2)->dog1.getAge() - dog2.getAge(); dogList.sort(c); dogList.forEach(System.out::println); }
3.舍去中間變量 c,進一步簡化代碼
/** * 舍去中間變量 c 的寫法 * */ static void lambda2(List<? extends Dog> dogList) { dogList.sort((dog1, dog2)->dog1.getAge() - dog2.getAge()); dogList.forEach(System.out::println); }
總結:基本上,我們第一次接觸 lambda 的話,都會去學習寫這個表達式,感覺使用起來特別的方便,達到了簡化代碼的目的。
接口中有一個靜態方法 comparing
,使用起來也特別的方便,基本上可以代替上面的那種方式了,它的參數為:Function<? super T, ? extends U> keyExtractor
,這需要傳入一個 lambda 表達式。雖然這些方法的定義很復雜,但是使用起來卻感覺很簡單,復雜的事情都被別人做了。
comparing 方法源碼
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)); }
1.使用 comparing方法創建 Comparator 對象
/** * 使用 Java 8 提供的靜態方法 comparing 方法, * 再配合方法引用,寫法更加簡潔了。但是看這個 * 方法,我們可能會疑問排序順序到底是正序還是逆序呢? * */ static void lambda3(List<? extends Dog> dogList) { dogList.sort(Comparator.comparing(Dog::getAge)); dogList.forEach(System.out::println); }
說明: 通過上面的源碼可以看到,c1 和 c2 的位置是固定的(排序是固定的升序方式),它是通過 Function 接口,調用apply方法,生成一個對象,然后調用 compareTo 方法進行比較的。(例如,我們傳進去的是age,類型為int,但是通過apply會返回 Integer類型。因為包裝類型和 String 類都實現了 Comparable 接口。)
注意: 如果不使用方法引用的話,那么 Dog::getAge
應該被替換為:
(dog1, dog2)->dog1.getAge() - dog2.getAge()
不過,這樣做顯然就是去了簡介性。
2.使用重載的 comparing 方法創建 Comparator 對象
comparing 方法源碼
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)); }
說明: 它比上面的 comparing 方法多了一個參數,意味著它可以實現更豐富的比較操作。而且,這個參數也是一個 Comparator 對象。
好了,下面使用這個方法,來實現按照年齡逆序排序。
/** * 解決 lambda3 中的疑問,關于排序順序的問題。 * 上面那個方法是一個簡便方法,它的排序是默認的正序, * 而我們有時會希望逆序排序。所以我們需要使用它的一個重載方法了。 * */ static void lambda4(List<? extends Dog> dogList) { //它的第二個參數,可能會引起困惑,第二個參數的類型就是第一個參數指定的類型(如果是基本類型,則為對應的包裝類) dogList.sort(Comparator.comparing(Dog::getAge, (age1, age2)->age2-age1)); dogList.forEach(System.out::println); }
注意: 這里的第二個參數中的 age1 和 age2 的實際類型為 Integer而不是 int,可以直接相減的原因是因為自動拆箱機制,所以這里推薦更換為:
說明: 這樣看起來,似乎不夠簡潔,下面將使用更加簡潔的方式來實現逆序排序。
(age1, age2)->age2.compareTo(age1)
運行結果:
3.使用comparing方法的更加簡潔形式
Comparator 具有一個靜態的方法,它的功能很簡單就是逆序。
/** * 相信看完 lambda4 都會感覺還沒有 lambda2 的方式簡潔呢, * 但是因為正序和逆序只是一個變換順序的問題,所以它也提 * 供了簡潔的實現。當然了,這也與我這里的使用的Dog對象,比較簡單有關, * 只看這里的話, 和上面 lambda2 進行比較,優勢不太明顯。 * */ static void lambda5(List<? extends Dog> dogList) { dogList.sort(Comparator.comparing(Dog::getAge, Comparator.reverseOrder())); dogList.forEach(System.out::println); }
這樣,代碼就顯得簡潔多了,當然了,還可以使用一個默認方法當到同樣的目的。
/** * 這樣也可以 * */ static void lambda55(List<? extends Dog> dogList) { dogList.sort(Comparator.comparing(Dog::getAge).reversed()); dogList.forEach(System.out::println); }
thenComparing 方法源碼:
default <U extends Comparable<? super U>> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor) { return thenComparing(comparing(keyExtractor)); }
有時候,會碰到這樣的需求,需要使用多種排序方法,而不是單純的一種。例如使用:姓名、年齡、體重進行排序。這時就需要使用 thenComparing 方法了。
/** * 實現按照多個標準排序:姓名、年齡、體重 * 全部按照自然排序(升序)的順序 * */ static void lambda7(List<? extends Dog> dogList) { dogList.sort(Comparator .comparing(Dog::getName) .thenComparing(Dog::getAge) .thenComparing(Dog::getWeight)); dogList.forEach(System.out::println); }
說明1: 這里按照三個條件排序是指如果姓名相同了,再按照下一個排序,以此類推,所以你可能看不出來差別(這個結果和按照姓名排序一樣,主要是排序的數據不太適合,但我不想換了。)。
說明2: 你仍然可以繼續添加更多的排序規則,因為 thenComparing 方法也有重載的方法。
運行結果:
thenComparing 方法的重載方法源碼:
default <U> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { return thenComparing(comparing(keyExtractor, keyComparator)); }
它的第二個方法,也和上面的 comparing 方法作用相同,是自己實現一個key的比較器,這里就不再說明了。
適用于 Int、long 和 double 類型的 thenComapring 方法
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) { return thenComparing(comparingInt(keyExtractor)); } default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) { return thenComparing(comparingLong(keyExtractor)); } default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) { return thenComparing(comparingDouble(keyExtractor)); }
說明:這幾個方法和上面的 thenComparing 方法作用基本相同,但是更加適合處理 int、long和double類型。如果需要排序的類型為這幾個,使用這些方法很好,但是我還是喜歡通用的 thenComparing
方法,這里只演示一個 thenComparingDouble
方法:
static void thenComparingDouble() { List<Dog> dogList = new ArrayList<>(); dogList.add(new Dog("小黑", 3, 37.0)); dogList.add(new Dog("二哈", 2, 55.0)); dogList.add(new Dog("泰迪", 1, 8.0)); dogList.add(new Dog("大黃", 2, 40.0)); dogList.sort(Comparator .comparing(Dog::getAge) .thenComparingDouble(Dog::getWeight)); dogList.forEach(System.out::println); }
運行結果:
如果去掉 thenComparingDouble 方法,運行結果為:
注意和上面的結果對比。
適用于 Int、long 和 double 類型的 comapring 方法
這三個方法,也是專門用于處理 int、long 和double類型的,和使用 comparing方法差不多。
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2)); } public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2)); } public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)); }
這里演示 comparingInt
和 comparingDouble
兩個方法的用法:
我感覺沒什么區別,可能是我這個測試用例太簡單了吧。
/** * comparingToInt * */ static void comparingToInt(List<? extends Dog> dogList) { dogList.sort(Comparator.comparingInt(Dog::getAge)); dogList.forEach(System.out::println); } /** * comparingToDouble * */ static void comparingToDouble(List<? extends Dog> dogList) { dogList.sort(Comparator.comparingDouble(Dog::getWeight)); dogList.forEach(System.out::println); }
static void nullSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add(null); strList.add("Bird"); strList.add(null); strList.sort(Comparator.comparing(String::length)); strList.forEach(System.out::println); }
運行上面的代碼,結果為:
說明:null 值是一個很頭疼的問題,所以 Comparator接口也專門提供了處理null值得方法,它們都是對 null 值友好的方法(null-friendly)。
//null 值在前面。 //Returns a null-friendly comparator that considers null to be less than non-null. public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(true, comparator); } //null 值在后面。 //Returns a null-friendly comparator that considers null to be greater than non-null. public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(false, comparator); }
因此,對于含有null值的元素進行排序,可以這樣做:
/** * 含有 null 值得元素排序 * */ public static void nullValueSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add(null); strList.add("Bird"); strList.add(null); //我一開始以為是一個字符常量呢?但是一想不對勁,原來是一個靜態常量比較器。 //這個是 String 類的比較器:CASE_INSENSITIVE_ORDER //null 值在前排序 strList.sort(Comparator.nullsFirst(String.CASE_INSENSITIVE_ORDER)); strList.forEach(System.out::println); System.out.println("===================分隔符===================="); //null 值在后排序 strList.sort(Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)); strList.forEach(System.out::println); }
運行結果:
注:擺脫了,煩人的NullPointerException,哈哈。
in other words, it returns a comparator that imposes the reverse of the natural ordering on a collection of objects that implement the Comparable interface。
換言之,它返回一個比較器,該比較器對實現可比較接口的對象集合施加與自然順序相反的順序。
說明: 由于它是默認方法,所以必須由比較器對象本身來調用,正好可以實現逆序操作??梢栽趧摻ū容^器后繼續調用這個方法,就可以實現逆序了。但是要注意它調用的順序,它和下面這個 reverseOrder 方法還是不一樣的,下面這個方法是靜態方法,可以通過類直接調用。注意,用法上的區別就是了。
default Comparator<T> reversed() { return Collections.reverseOrder(this); }
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); }
如果直接使用這個方法,創建比較器對象的話,那么集合里面的元素必須使用 Comparable 接口。
static void reverseSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add("Bird"); strList.sort(Comparator.reverseOrder()); strList.forEach(System.out::println); }
注意:這里有一個很有趣的地方,這個方法Comparator.reverseOrder()
無法使用方法引用改寫:Comparator::reverseOrder
,具體原因我看了,但是不是太理解,就不說了。
運行結果:
補充: 晚上思考了一下,結合別人的答案,這里其實也是不難理解的。自所以不能使用方法引用,是因為它根本就不是 lambda 表達式。Lambda 表達式需要依賴一個函數式接口,也就是 Comparator 接口。它的作用就是一個簡化,所以它的需要的參數就是 int compare(T o1, T o2);
的方法中的參數。
所以,如果這樣寫的話,會報一個錯誤。
The type Comparator does not define reverseOrder(String, String) that is applicable here
strList.sort(Comparator::reverseOrder);
因此,上面這個寫法就是錯誤的了。它并不能使用lambda的形式改寫。
reverseOrder 和 reversed聯合使用
static void reverseSort() { List<String> strList = new ArrayList<>(); strList.add("dog"); strList.add("cat"); strList.add("Bird"); Comparator c = Comparator.reverseOrder().reversed(); strList.sort(c); strList.forEach(System.out::println); }
說明:上面這個例子我不會添加泛型了,我無論怎么添加都是錯誤的,但是如果不添加泛型的話,那么編譯就能通過了。但是這個東西的泛型似乎很奇怪,我也不太明白了,但是這個方法很有趣,反序的反序又是正序了。(這里存粹是娛樂一下,但是好像發現了好玩的東西。)
運行結果:
naturalOrder
定制排序里面居然有一個方法名叫做自然排序,這個方法感覺很有趣。但是使用的話,需要抑制一下 unchecked
警告。
@SuppressWarnings("unchecked") public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; }
方法注釋里面說明了:
@param <T> the {@link Comparable} type of element to be compared。
參數必須是 Comparable類型的,即實現 Comparable 接口。
自然排序:
/** * Comparator 實現自然排序 * */ @SuppressWarnings("unchecked") static void lambda6(List<? extends Dog> dogList) { dogList.sort((Comparator<Dog>) Comparator.naturalOrder()); dogList.forEach(System.out::println); }
如果直接運行這個方法會產生問題,必須要先實現 Comparable接口才行,并重寫 compareTo
方法。
@Override public int compareTo(Dog o) { return age-o.age; }
運行結果:
以上就是關于“怎么使用Lambda表達式簡化Comparator的使用問題”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。