欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java函數(shù)式編程(九):Comparator

 更新時(shí)間:2014年09月26日 09:21:29   作者:有孚  
這篇文章主要介紹了Java函數(shù)式編程(九):Comparator,本文是系列文章的第9篇,其它文章請參閱本文底部的相關(guān)文章,需要的朋友可以參考下

實(shí)現(xiàn)Comparator接口

Comparator接口的身影在JDK庫中隨處可見,從查找到排序,再到反轉(zhuǎn)操作,等等。Java 8里它變成了一個(gè)函數(shù)式接口,這樣的好處就是我們可以使用流式語法來實(shí)現(xiàn)比較器了。

我們用幾種不同的方式來實(shí)現(xiàn)一下Comparator,看看新式語法的價(jià)值所在。你的手指頭會(huì)感謝你的,不用實(shí)現(xiàn)匿名內(nèi)部類少敲了多少鍵盤啊。

使用Comparator進(jìn)行排序

下面這個(gè)例子將使用不同的比較方法,來將一組人進(jìn)行排序。我們先來創(chuàng)建一個(gè)Person的JavaBean。

復(fù)制代碼 代碼如下:

public class Person {
private final String name;
private final int age;
public Person(final String theName, final int theAge) {
name = theName;
age = theAge;
}
public String getName() { return name; }
public int getAge() { return age; }
public int ageDifference(final Person other) {
return age - other.age;
}
public String toString() {
return String.format("%s - %d", name, age);
}
}

我們可以通過Person類來實(shí)現(xiàn)Comparator接口,不過這樣我們只能使用一種比較方式。我們希望能比較不同的屬性——比如名字,年齡,或者這些的組合。為了可以靈活的進(jìn)行比較,我們可以使用Comparator,當(dāng)我們需要進(jìn)行比較的時(shí)候,再去生成相關(guān)的代碼。

我們先來創(chuàng)建一個(gè)Person的列表,每個(gè)人都有不同的名字和年齡。

復(fù)制代碼 代碼如下:

final List<Person> people = Arrays.asList(
new Person("John", 20),
new Person("Sara", 21),
new Person("Jane", 21),
new Person("Greg", 35));

我們可以通過人的名字或者年齡來對他們進(jìn)行升序或者降序的排序。一般的方法就是使用匿名內(nèi)部類來實(shí)現(xiàn)Comparator接口。這樣寫的話只有比較相關(guān)的代碼是有意義的,其它的都只是走走形式而已。而使用lambda表達(dá)式則可以聚焦到比較的本質(zhì)上來。

我們先按年齡從小到大對他們進(jìn)行排序。

既然我們已經(jīng)有了一個(gè)List對象,我們可以用它的sort()方法來進(jìn)行排序。不過這個(gè)方法也有它的問題。這是一個(gè)void方法,也就是說當(dāng)我們調(diào)用這個(gè)方法的時(shí)候,這個(gè)列表會(huì)發(fā)生改動(dòng)。要保留原始列表的話,我們得先拷貝出一份來,然后再調(diào)用sort()方法。這簡直太費(fèi)勁了。這個(gè)時(shí)候我們得求助下Stream類了。

我們可以從List那獲取一個(gè)Stream對象,然后調(diào)用它的sorted()方法。它會(huì)返回一個(gè)排好序的集合,而不是在原來的集合上做修改。使用這個(gè)方法的話可以很方便的配置Comparator的參數(shù)。

復(fù)制代碼 代碼如下:

List<Person> ascendingAge =
people.stream()
.sorted((person1, person2) -> person1.ageDifference(person2))
.collect(toList());
printPeople("Sorted in ascending order by age: ", ascendingAge);

我們先通過stream()方法將列表轉(zhuǎn)化成一個(gè)Stream對象。然后調(diào)用它的sorted()方法。這個(gè)方法接受一個(gè)Comparator參數(shù)。由于Comparator是一個(gè)函數(shù)式接口,我們可以傳入一個(gè)lambda表達(dá)式。最后我們調(diào)用collect方法,讓它把結(jié)果存儲到一個(gè)列表里。collect方法是一個(gè)歸約器,它能把迭代過程中的對象輸出成某種特定的格式或者類型。toList()方法是Collectors類的一個(gè)靜態(tài)方法。

Comparator的抽象方法compareTo()接收兩個(gè)參數(shù),也就是要比較的對象,并返回一個(gè)int類型的結(jié)果。為了兼容這個(gè),我們的lambda表達(dá)式也接收兩個(gè)參數(shù),兩個(gè)Person對象,它們的類型是由編譯器自動(dòng)推導(dǎo)的。我們返回一個(gè)int類型,表明比較的對象是否相等。

因?yàn)橐茨挲g進(jìn)行排序,所以我們會(huì)比較兩個(gè)對象的年齡,然后返回比較的結(jié)果。如果他們一樣大,則返回0。否則如果第一個(gè)人更年輕的話就返回一個(gè)負(fù)數(shù),更年長的話就返回正數(shù)。

sorted()方法會(huì)遍歷目標(biāo)集合的每個(gè)元素并調(diào)用指定的Comparator,來確定出元素的排序順序。sorted()方法的執(zhí)行方式有點(diǎn)類似前面說到的reduce()方法。reduce()方法把列表逐步歸約出一個(gè)結(jié)果。而sorted()方法則通過比較的結(jié)果來進(jìn)行排序。

一旦我們排好序后我們想要把結(jié)果打印出來,因此我們調(diào)用了一個(gè)printPeople()方法;下面來實(shí)現(xiàn)下這個(gè)方法。

復(fù)制代碼 代碼如下:

public static void printPeople(
final String message, final List<Person> people) {
System.out.println(message);
people.forEach(System.out::println);
}

這個(gè)方法中,我們先打印了一個(gè)消息,然后遍歷列表,打印出里面的每個(gè)元素。

我們來調(diào)用下sorted()方法看看,它會(huì)將列表中的人按年齡從小到大進(jìn)行排列。

復(fù)制代碼 代碼如下:

Sorted in ascending order by age:
John - 20
Sara - 21
Jane - 21
Greg - 35

我們再看一下sorted()方法,來做一個(gè)改進(jìn)。

復(fù)制代碼 代碼如下:

.sorted((person1, person2) -> person1.ageDifference(person2))

在傳入的這個(gè)lambda表達(dá)式里,我們只是簡單的路由了下這兩個(gè)參數(shù)——第一個(gè)參數(shù)作為ageDifference()方法的調(diào)用目標(biāo),而第二個(gè)作為它的參數(shù)。但是我們可以不這么寫,而是用一個(gè)office-space模式——也就是使用方法引用,讓Java編譯器去做路由。

這里用到的參數(shù)路由和前面看到的有點(diǎn)不同。我們之前看到的,要么參數(shù)是作為調(diào)用目標(biāo),要么是作為調(diào)用參數(shù)。而現(xiàn)在,我們有兩個(gè)參數(shù),我們希望能分成兩個(gè)部分,一個(gè)是作為方法調(diào)用的目標(biāo),第二個(gè)則作為參數(shù)。別擔(dān)心,Java編譯器會(huì)告訴你,“這個(gè)我來搞定”。

我們可以把前面的sorted()方法里面的lambda表達(dá)式替換成一個(gè)短小精悍的ageDifference方法。

復(fù)制代碼 代碼如下:

people.stream()
.sorted(Person::ageDifference)

這段代碼非常簡潔,這多虧了Java編譯器提供的方法引用。編譯器接收到兩個(gè)person實(shí)例的參數(shù),把第一個(gè)用作ageDifference()方法的調(diào)用目標(biāo),而第二個(gè)作為方法參數(shù)。我們讓編譯器去做這個(gè)工作,而不是自己直接去寫代碼。當(dāng)使用這種方式的時(shí)候,我們必須確定第一個(gè)參數(shù)就是引用的方法的調(diào)用目標(biāo),而剩下那個(gè)就是方法的入?yún)ⅰ?/p>

重用Comparator

我們很容易就將列表中的人按年齡從小到大排好序了,當(dāng)然從大到小進(jìn)行排序也很容易。我們來試一下。

復(fù)制代碼 代碼如下:

printPeople("Sorted in descending order by age: ",
people.stream()
.sorted((person1, person2) -> person2.ageDifference(person1))
.collect(toList()));

我們調(diào)用了sorted()方法并傳入一個(gè)lambda表達(dá)式,它正好能適配成Comparator接口,就像前面的例子那樣。唯一不同的就是這個(gè)lambda表達(dá)式的實(shí)現(xiàn)——我們把要比較的人調(diào)了下順序。結(jié)果應(yīng)該是按他們的年齡由從大到小排列的。我們來看一下。

復(fù)制代碼 代碼如下:

Sorted in descending order by age:
Greg - 35
Sara - 21
Jane - 21
John - 20

只是改一下比較的邏輯費(fèi)不了太多勁。但我們沒法把這個(gè)版本重構(gòu)成方法引用的,因?yàn)閰?shù)的順序不符合方法引用的參數(shù)路由的規(guī)則;第一個(gè)參數(shù)并不是用作方法的調(diào)用目標(biāo),而是作為方法參數(shù)。有一個(gè)方法能解決這個(gè)問題,同時(shí)它還能減少重復(fù)的工作。我們來看下如何實(shí)現(xiàn)。

前面我們已經(jīng)創(chuàng)建了兩個(gè)lambda表達(dá)式:一個(gè)是按年齡從小到大排序,一個(gè)是從大到小排序。這么做的話,會(huì)出現(xiàn)代碼冗余和重復(fù),并破壞了DRY原則。如果我們只是想要調(diào)整下排序順序的話,JDK提供了一個(gè)reverse方法,它有一個(gè)特殊的方法修飾符,default。我們會(huì)在77頁中的default方法來討論它,這里我們先用下這個(gè)reversed()方法來去除冗余性。

復(fù)制代碼 代碼如下:

Comparator<Person> compareAscending =
(person1, person2) -> person1.ageDifference(person2);
Comparator<Person> compareDescending = compareAscending.reversed();

我們先創(chuàng)建了一個(gè)Comparator,compareAscending,來將人按年齡從小到大進(jìn)行排序。為了反轉(zhuǎn)比較順序,而不是再寫一次這個(gè)代碼,我們只需要調(diào)用一下這個(gè)第一個(gè)Comparator的reversed()方法就可以獲取第二個(gè)Comparator對象。在reversed()方法底層,它創(chuàng)建了一個(gè)比較器,來交換了比較的參數(shù)的順序。這說明reversed也是一個(gè)高階方法——它創(chuàng)建并返回了一個(gè)無副作用的函數(shù)。我們把這個(gè)兩個(gè)比較器用到代碼里。
復(fù)制代碼 代碼如下:

printPeople("Sorted in ascending order by age: ",
      people.stream()
    
    
.sorted(compareAscending)
    
    
.collect(toList())
);
printPeople("Sorted in descending order by age: ",
people.stream()
.sorted(compareDescending)
.collect(toList())
);

從代碼中明顯可以看到,Java8的這些新特性極大的減少了代碼的冗余及復(fù)雜度,不過好處遠(yuǎn)不止這些,JDK里還有無限可能等著你去探索。

我們已經(jīng)可以按年齡進(jìn)行排序了,想按名字來排序的話也很簡單。我們來按名字進(jìn)行字典序排列,同樣的,只需要改下lambda表達(dá)式里的邏輯就好了。

復(fù)制代碼 代碼如下:

printPeople("Sorted in ascending order by name: ",
people.stream()
.sorted((person1, person2) ->
person1.getName().compareTo(person2.getName()))
.collect(toList()));

輸出的結(jié)果里會(huì)按名字的字典序進(jìn)行排列。
復(fù)制代碼 代碼如下:

Sorted in ascending order by name:
Greg - 35
Jane - 21
John - 20
Sara - 21

現(xiàn)在為止,我們要么就按年齡排序,要么就按名字排序。我們可以讓lambda表達(dá)式的邏輯更智能一些。比如我們可以同時(shí)按年齡和名字排序。

我們來選出列表中最年輕的人來。我們可以先按年齡從小到大排序然后選中結(jié)果中的第一個(gè)。不過其實(shí)用不著那樣,Stream有一個(gè)min()方法可以實(shí)現(xiàn)這個(gè)。這個(gè)方法同樣也接受一個(gè)Comparator,不過返回的是集合中最小的對象。我們來用下它。

復(fù)制代碼 代碼如下:

people.stream()
.min(Person::ageDifference)
.ifPresent(youngest -> System.out.println("Youngest: " + youngest));

調(diào)用min()方法的時(shí)候我們用了ageDifference這個(gè)方法引用。min()方法返回的是一個(gè)Optinal對象,因?yàn)榱斜砜赡転榭詹⑶依锩婵赡懿恢挂粋€(gè)年紀(jì)最小的人。接著我們通過Optinal的ifPrsend()方法獲取到年紀(jì)最小的那個(gè)人,并打印出他的詳細(xì)信息。來看下輸出結(jié)果。
復(fù)制代碼 代碼如下:

Youngest: John - 20

輸出年紀(jì)最大的同樣也很簡單。只要把這個(gè)方法引用傳給一個(gè)max()方法就好了。

復(fù)制代碼 代碼如下:

people.stream()
.max(Person::ageDifference)
.ifPresent(eldest -> System.out.println("Eldest: " + eldest));

我們來看下最年長那位的名字和年齡。
復(fù)制代碼 代碼如下:

Eldest: Greg - 35

有了lambda表達(dá)式和方法引用之后,比較器的實(shí)現(xiàn)變得更簡潔也更方便了。JDK也給Compararor類引入了不少便利的方法,使得我們可以更流暢的進(jìn)行比較,下面我們將會(huì)看到。

多重比較和流式比較

我們來看下Comparator接口提供了哪些方便的新方法,并用它們來進(jìn)行多個(gè)屬性的比較。

我們還是繼續(xù)使用上節(jié)中的那個(gè)例子。按名字排序的話,我們上面是這么寫的:

復(fù)制代碼 代碼如下:

people.stream()
.sorted((person1, person2) ->
person1.getName().compareTo(person2.getName()));

和上個(gè)世紀(jì)的內(nèi)部類的寫法比起來,這種寫法簡直太簡潔了。不過如果用了Comparator類里面的一些函數(shù)能讓它變得更簡單,使用這些函數(shù)能夠讓我們更流暢的表述自己的目的。比如說,要按名字排序的話,我們可以這么寫:

復(fù)制代碼 代碼如下:

final Function<Person, String> byName = person -> person.getName();
people.stream()
.sorted(comparing(byName));

這段代碼中我們導(dǎo)入了Comparator類的靜態(tài)方法comparing()。comparing()方法使用傳入的lambda表達(dá)式來生成一個(gè)Comparator對象。也就是說,它也是一個(gè)高階函數(shù),接受一個(gè)函數(shù)入?yún)⒉⒎祷亓硪粋€(gè)函數(shù)。除了能讓語法變得更簡潔外,這樣的代碼讀起來也能更好的表述我們想要解決的實(shí)際問題。

有了它,進(jìn)行多重比較的時(shí)候也能變得更加流暢。比如,下面這段按名字和年齡比較的代碼就能說明一切:

復(fù)制代碼 代碼如下:

final Function<Person, Integer> byAge = person -> person.getAge();
final Function<Person, String> byTheirName = person -> person.getName();
printPeople("Sorted in ascending order by age and name: ",
people.stream()
.sorted(comparing(byAge).thenComparing(byTheirName))
.collect(toList()));

我們先是創(chuàng)建了兩個(gè)lambda表達(dá)式,一個(gè)返回指定人的年齡,一個(gè)返回的是他的名字。在調(diào)用sorted()方法的時(shí)候我們把這兩個(gè)表達(dá)式組合 到了一起,這樣就能進(jìn)行多個(gè)屬性的比較了。comparing()方法創(chuàng)建并返回了一個(gè)按年齡比較的Comparator ,我們再調(diào)用這個(gè)返回的Comparator上面的thenComparing()方法來創(chuàng)建一個(gè)組合的比較器,它會(huì)對年齡和名字兩項(xiàng)進(jìn)行比較。下面的輸出是先按年齡再按名字進(jìn)行排序后的結(jié)果。

復(fù)制代碼 代碼如下:

Sorted in ascending order by age and name:
John - 20
Jane - 21
Sara - 21
Greg - 35

可以看到,使用lambda表達(dá)式和JDK提供的新的工具類,可以很容易的將Comparator的實(shí)現(xiàn)進(jìn)行組合。下面我們來介紹下Collectors。

相關(guān)文章

  • Spring MVC中基于自定義Editor的表單數(shù)據(jù)處理技巧分享

    Spring MVC中基于自定義Editor的表單數(shù)據(jù)處理技巧分享

    Spring MVC中基于自定義Editor的表單數(shù)據(jù)處理技巧。需要的朋友可以過來參考下,希望對大家有所幫助
    2013-12-12
  • Java中RabbitMQ隊(duì)列實(shí)現(xiàn)RPC詳解

    Java中RabbitMQ隊(duì)列實(shí)現(xiàn)RPC詳解

    這篇文章主要介紹了Java中RabbitMQ隊(duì)列實(shí)現(xiàn)RPC詳解,在本教程中,我們將使用RabbitMQ構(gòu)建一個(gè)RPC系統(tǒng):一個(gè)客戶端和一個(gè)RPC服務(wù)器,我們將創(chuàng)建一個(gè)返回斐波那契數(shù)字的模擬RPC服務(wù),,需要的朋友可以參考下
    2023-08-08
  • IDEA新手必備之各種快捷鍵詳解

    IDEA新手必備之各種快捷鍵詳解

    這篇文章主要介紹了IDEA新手必備之各種快捷鍵詳解,文中有非常詳細(xì)的快捷鍵介紹,對正在使用IDEA的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • Java 求解如何把二叉搜索樹轉(zhuǎn)換為累加樹

    Java 求解如何把二叉搜索樹轉(zhuǎn)換為累加樹

    這篇文章主要介紹了Java 求解把二叉搜索樹轉(zhuǎn)換為累加樹的代碼,總之需要觀察示例節(jié)點(diǎn)的規(guī)律,需要記錄上個(gè)節(jié)點(diǎn)的情況,注意引入前驅(qū)節(jié)點(diǎn)pre,具體實(shí)例代碼跟隨小編一起看看吧
    2021-11-11
  • SpringBoot 如何編寫配置文件

    SpringBoot 如何編寫配置文件

    這篇文章主要介紹了SpringBoot 編寫配置文件的兩種方法,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-11-11
  • 一文帶你深入了解Java的自動(dòng)拆裝箱

    一文帶你深入了解Java的自動(dòng)拆裝箱

    Java推出了對于基本數(shù)據(jù)類型的對應(yīng)的對象,將基本數(shù)據(jù)類型轉(zhuǎn)換為對象就稱為裝箱,反之則是拆箱,本文主要為大家介紹了Java自動(dòng)拆裝箱的原理與應(yīng)用,需要的可以參考下
    2023-11-11
  • SpringMVC中的HandlerMapping詳解

    SpringMVC中的HandlerMapping詳解

    這篇文章主要介紹了SpringMVC中的HandlerMapping詳解,HandlerMapping是請求映射處理器,也就是通過請求的url找到對應(yīng)的邏輯處理單元(Controller),注意這里只是建立請求與Controller的映射關(guān)系,最終的處理是通過HandlerAdapt來進(jìn)行處理的,需要的朋友可以參考下
    2023-09-09
  • 關(guān)于Spring總結(jié)(必看篇)

    關(guān)于Spring總結(jié)(必看篇)

    下面小編就為大家?guī)硪黄P(guān)于Spring總結(jié)(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-08-08
  • 詳解Java如何優(yōu)雅的調(diào)用dubbo同時(shí)不使用其它jar包

    詳解Java如何優(yōu)雅的調(diào)用dubbo同時(shí)不使用其它jar包

    這篇文章主要介紹了如何在不使用他人jar包的情況下優(yōu)雅的進(jìn)行dubbo調(diào)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-02-02
  • springboot使用DynamicDataSource動(dòng)態(tài)切換數(shù)據(jù)源的實(shí)現(xiàn)過程

    springboot使用DynamicDataSource動(dòng)態(tài)切換數(shù)據(jù)源的實(shí)現(xiàn)過程

    這篇文章主要給大家介紹了關(guān)于springboot使用DynamicDataSource動(dòng)態(tài)切換數(shù)據(jù)源的實(shí)現(xiàn)過程,Spring Boot應(yīng)用中可以配置多個(gè)數(shù)據(jù)源,并根據(jù)注解靈活指定當(dāng)前使用的數(shù)據(jù)源,需要的朋友可以參考下
    2023-08-08

最新評論