程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java函數式編程(九):Comparator

Java函數式編程(九):Comparator

編輯:關於JAVA

Java函數式編程(九):Comparator。本站提示廣大學習愛好者:(Java函數式編程(九):Comparator)文章只能為提供參考,不一定能成為您想要的結果。以下是Java函數式編程(九):Comparator正文


完成Comparator接口

Comparator接口的身影在JDK庫中到處可見,從查找到排序,再到反轉操作,等等。Java 8裡它釀成了一個函數式接口,如許的利益就是我們可使用流式語法來完成比擬器了。

我們用幾種分歧的方法來完成一下Comparator,看看舊式語法的價值地點。你的手指頭會感激你的,不消完成匿名外部類少敲了若干鍵盤啊。

應用Comparator停止排序

上面這個例子將應用分歧的比擬辦法,來將一組人停止排序。我們先來創立一個Person的JavaBean。


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類來完成Comparator接口,不外如許我們只能應用一種比擬方法。我們願望能比擬分歧的屬性——好比名字,年紀,或許這些的組合。為了可以靈巧的停止比擬,我們可使用Comparator,當我們須要停止比擬的時刻,再去生成相干的代碼。

我們先來創立一個Person的列表,每一個人都有分歧的名字和年紀。


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

我們可以經由過程人的名字或許年紀來對他們停止升序或許降序的排序。普通的辦法就是應用匿名外部類來完成Comparator接口。如許寫的話只要比擬相干的代碼是成心義的,其它的都只是逛逛情勢罷了。而應用lambda表達式則可以聚焦到比擬的實質下去。

我們先按年紀從小到年夜對他們停止排序。

既然我們曾經有了一個List對象,我們可以用它的sort()辦法來停止排序。不外這個辦法也有它的成績。這是一個void辦法,也就是說當我們挪用這個辦法的時刻,這個列表會產生修改。要保存原始列表的話,我們得先拷貝出一份來,然後再挪用sort()辦法。這的確太費力了。這個時刻我們得乞助下Stream類了。

我們可以從List那獲得一個Stream對象,然後挪用它的sorted()辦法。它會前往一個排好序的聚集,而不是在本來的聚集上做修正。應用這個辦法的話可以很便利的設置裝備擺設Comparator的參數。

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

我們先經由過程stream()辦法將列表轉化成一個Stream對象。然後挪用它的sorted()辦法。這個辦法接收一個Comparator參數。因為Comparator是一個函數式接口,我們可以傳入一個lambda表達式。最初我們挪用collect辦法,讓它把成果存儲到一個列內外。collect辦法是一個歸約器,它能把迭代進程中的對象輸入成某種特定的格局或許類型。toList()辦法是Collectors類的一個靜態辦法。

Comparator的籠統辦法compareTo()吸收兩個參數,也就是要比擬的對象,並前往一個int類型的成果。為了兼容這個,我們的lambda表達式也吸收兩個參數,兩個Person對象,它們的類型是由編譯器主動推導的。我們前往一個int類型,注解比擬的對象能否相等。

由於要按年紀停止排序,所以我們會比擬兩個對象的年紀,然後前往比擬的成果。假如他們一樣年夜,則前往0。不然假如第一小我更年青的話就前往一個正數,更年長的話就前往負數。

sorted()辦法會遍歷目的聚集的每一個元素並挪用指定的Comparator,來肯定出元素的排序次序。sorted()辦法的履行方法有點相似後面說到的reduce()辦法。reduce()辦法把列表慢慢歸約出一個成果。而sorted()辦法則經由過程比擬的成果來停止排序。

一旦我們排好序後我們想要把成果打印出來,是以我們挪用了一個printPeople()辦法;上面來完成下這個辦法。


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

這個辦法中,我們先打印了一個新聞,然後遍歷列表,打印出外面的每一個元素。

我們來挪用下sorted()辦法看看,它會將列表中的人按年紀從小到年夜停止分列。

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

我們再看一下sorted()辦法,來做一個改良。

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

在傳入的這個lambda表達式裡,我們只是簡略的路由了下這兩個參數——第一個參數作為ageDifference()辦法的挪用目的,而第二個作為它的參數。然則我們可以不這麼寫,而是用一個office-space形式——也就是應用辦法援用,讓Java編譯器去做路由。

這裡用到的參數路由和後面看到的有點分歧。我們之前看到的,要末參數是作為挪用目的,要末是作為挪用參數。而如今,我們有兩個參數,我們願望能分紅兩個部門,一個是作為辦法挪用的目的,第二個則作為參數。別擔憂,Java編譯器會告知你,“這個我來弄定”。

我們可以把後面的sorted()辦法外面的lambda表達式調換成一個短小精干的ageDifference辦法。

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

這段代碼異常簡練,這多虧了Java編譯器供給的辦法援用。編譯器吸收到兩個person實例的參數,把第一個用作ageDifference()辦法的挪用目的,而第二個作為辦法參數。我們讓編譯器去做這個任務,而不是本身直接去寫代碼。當應用這類方法的時刻,我們必需肯定第一個參數就是援用的辦法的挪用目的,而剩下誰人就是辦法的入參。

重用Comparator

我們很輕易就將列表中的人按年紀從小到年夜排好序了,固然從年夜到小停止排序也很輕易。我們來試一下。

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

我們挪用了sorted()辦法並傳入一個lambda表達式,它正好能適配成Comparator接口,就像後面的例子那樣。獨一分歧的就是這個lambda表達式的完成——我們把要比擬的人調了下次序。成果應當是按他們的年紀由從年夜到小分列的。我們來看一下。

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

只是改一下比擬的邏輯費不了太多勁。但我們沒法把這個版本重組成辦法援用的,由於參數的次序不相符辦法援用的參數路由的規矩;第一個參數其實不是用作辦法的挪用目的,而是作為辦法參數。有一個辦法能處理這個成績,同時它還能削減反復的任務。我們來看下若何完成。

後面我們曾經創立了兩個lambda表達式:一個是按年紀從小到年夜排序,一個是從年夜到小排序。這麼做的話,會湧現代碼冗余和反復,並損壞了DRY准繩。假如我們只是想要調劑下排序次序的話,JDK供給了一個reverse辦法,它有一個特別的辦法潤飾符,default。我們會在77頁中的default辦法來評論辯論它,這裡我們先用下這個reversed()辦法往來來往除冗余性。


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

我們先創立了一個Comparator,compareAscending,來將人按年紀從小到年夜停止排序。為了反轉比擬次序,而不是再寫一次這個代碼,我們只須要挪用一下這個第一個Comparator的reversed()辦法便可以獲得第二個Comparator對象。在reversed()辦法底層,它創立了一個比擬器,來交流了比擬的參數的次序。這解釋reversed也是一個高階辦法——它創立並前往了一個無反作用的函數。我們把這個兩個比擬器用到代碼裡。

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的這些新特征極年夜的削減了代碼的冗余及龐雜度,不外利益遠不止這些,JDK裡還有沒有限能夠等著你去摸索。

我們曾經可以按年紀停止排序了,想按名字來排序的話也很簡略。我們來按名字停止字典序分列,異樣的,只須要改下lambda表達式裡的邏輯就行了。

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

輸入的成果裡會按名字的字典序停止分列。

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

如今為止,我們要末就按年紀排序,要末就按名字排序。我們可讓lambda表達式的邏輯更智能一些。好比我們可以同時按年紀和名字排序。

我們來選出列表中最年青的人來。我們可以先按年紀從小到年夜排序然後選中成果中的第一個。不外其適用不著那樣,Stream有一個min()辦法可以完成這個。這個辦法異樣也接收一個Comparator,不外前往的是聚集中最小的對象。我們來用下它。

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

挪用min()辦法的時刻我們用了ageDifference這個辦法援用。min()辦法前往的是一個Optinal對象,由於列表能夠為空而且外面能夠不止一個年事最小的人。接著我們經由過程Optinal的ifPrsend()辦法獲得到年事最小的誰人人,並打印出他的具體信息。來看下輸入成果。

Youngest: John - 20

輸入年事最年夜的異樣也很簡略。只需把這個辦法援用傳給一個max()辦法就行了。

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

我們來看下最年長那位的名字和年紀。

Eldest: Greg - 35

有了lambda表達式和辦法援用以後,比擬器的完成變得更簡練也更便利了。JDK也給Compararor類引入了很多方便的辦法,使得我們可以更流利的停止比擬,上面我們將會看到。

多重比擬和流式比擬

我們來看下Comparator接口供給了哪些便利的新辦法,並用它們來停止多個屬性的比擬。

我們照樣持續應用上節中的誰人例子。按名字排序的話,我們下面是這麼寫的:

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

和上個世紀的外部類的寫法比起來,這類寫法的確太簡練了。不外假如用了Comparator類外面的一些函數能讓它變得更簡略,應用這些函數可以或許讓我們更流利的表述本身的目標。好比說,要按名字排序的話,我們可以這麼寫:

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

這段代碼中我們導入了Comparator類的靜態辦法comparing()。comparing()辦法應用傳入的lambda表達式來生成一個Comparator對象。也就是說,它也是一個高階函數,接收一個函數入參並前往另外一個函數。除能讓語法變得更簡練外,如許的代碼讀起來也能更好的表述我們想要處理的現實成績。

有了它,停止多重比擬的時刻也能變得加倍流利。好比,上面這段按名字和年紀比擬的代碼就可以解釋一切:

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()));

我們先是創立了兩個lambda表達式,一個前往指定人的年紀,一個前往的是他的名字。在挪用sorted()辦法的時刻我們把這兩個表達式組合 到了一路,如許就可以停止多個屬性的比擬了。comparing()辦法創立並前往了一個按年紀比擬的Comparator ,我們再挪用這個前往的Comparator下面的thenComparing()辦法來創立一個組合的比擬器,它會對年紀和名字兩項停止比擬。上面的輸入是先按年紀再按名字停止排序後的成果。

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

可以看到,應用lambda表達式和JDK供給的新的對象類,可以很輕易的將Comparator的完成停止組合。上面我們來引見下Collectors。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved