功能介面
目錄
1.簡介 2.理由 3. Lambda表達式 4.謂詞<T> 5. BiPredicate <T,U> 6.函數<T,R> 7. BiFunction <T,U,R> 8.消費者<T> 9. BiConsumer <T,U> 10.供應商<T> 11.一元運算符<T> 12. BinaryOperator <T>1.簡介
函數接口是Java 8提供的強大機制,使我們可以將函數作為參數傳遞給其他方法。 實際上,此選項在Java的早期版本中已經存在,例如,具有接口Comparator。
什么是功能接口? 功能接口定義了“對象”,它們不像傳統對象那樣存儲值,而僅存儲“功能”。 請注意,“對象”和“功能”都在引號之間,這是因為功能接口不是實際的對象或功能,而只是一種機制,該機制具有一種方法來接收功能元素作為參數。 讓我們回顧一下Comparator界面:Comparator定義了什么? 定義了自然排序標準,以使compare方法告訴我們兩個給定對象中的哪個可以視為較小對象。 如果將Comparator對象傳遞給方法,則我們將為該方法提供能夠告知對象順序的函數。 該方法相對于此排序是“通用的”,并且必須準備好接收任何條件并根據輸入的排序執行其功能。
這樣,例如,來自Collections類的sort方法可以接收Comparator類型的對象。 以前,必須已經實現了Comparator類型的類來定義要比較的類的對象的順序。
例。 根據價格從大到小的順序排列航班的Comparator類是:
public class ComparatorFlightPrice implements Comparator<Flight> {public int compare(Flight v1, Flight v2){return v1.getPrice().compareTo(v2.getPrice());} }調用可能是:
Collections.sort(flightList, new ComparatorFlightPrice());可以看出,由于接收到的Comparator對象告訴sort必須如何對flightList中的值進行排序,因此sort方法將接收一個函數類型參數。 很明顯,必須準備Collections類的sort方法的代碼以在不同條件下進行排序。 為此,sort方法在其代碼行中具有對其作為參數接收的對象的compare方法的一般調用。 這樣,如果它收到按價格定購的比較器,它將按航班價格定購輸入清單,而如果按乘客號傳遞通過的比較器定單,則這將成為訂購標準。
Java 8的作用是擴展功能接口的數量及其可用性,以定義一組參數為這些接口的方法。
2.理由
讓我們研究不同于Comparator的功能接口的另一種可能的應用。 有很多算法在其方案中使用布爾條件。 最簡單的例子之一是計數器算法模式,該模式返回滿足一定條件的集合中元素的數量:今天有多少次完整航班起飛? 本周有幾班飛往馬德里的航班? 等。我們知道此算法的方案如下:
Scheme counterInitiate counterFor each element in collection If conditionIncrement counterEndIfEndForReturn counter}該方案將元素集合和必須驗證的條件作為輸入,并將計數器作為輸出。 例如,讓我們看一下來自Airport的方法,該方法計算了前往特定目的地的航班數量以及特定日期之后的航班數量。
public Integer counterFlightsDate(Date f){Integer counter=0;for(Flight v:flights){if (v.getDate().compareTo(f)>0){counter++;}}return counter;}public Integer counterFlightsDestination(String d){Integer counter =0;for(Flight v:flights){if (v.getDestination().equals(d)){counter ++;}}return counter;}顯然,除了條件和參數類型外,這兩種方法的代碼完全相同:第一種情況的Date f和第二種情況的目標String d。 此代碼將按以下方式調用:
Integer cont1 = SVQairport.counterFlightsDate("Madrid"); System.out.println("The number of flights to Madrid is "+cont1); Integer cont2 = SVQairport.counterFlightsDestination(new Date(16,07,2014)); System.out.println("The number of flights later than 16th of July is "+cont2);讓我們假設我們可以通過在“ if”上指定的條件作為參數。 然后,方法代碼可以推廣為以下內容:
public Integer genericalFlightCount(Condition<Flight> filter){Integer counter=0;for (Flight v:flights){if (condition of filter over v){counter ++;}}return counter;} 第1行:稍后,此假設類型的Condition實際上將是謂詞類型
第4行:此表達式實際上是filter.test(v),因為這是Predicate中實現的方法
這樣,我們將擁有一個針對Airport的通用計數器方法,該方法一旦編碼,便可以使用不同的布爾表達式調用并具有不同的功能。 為了能夠將布爾表達式作為參數傳遞,我們需要一個類型(接口)條件,該條件將實現方法的接收和所涉及類型的對象(在這種情況下為Flight),并返回布爾類型的值符合要求的條件。 功能接口謂詞就是這種可能性,它將是下一部分要研究的第一個示例。
該通用方法的調用為:
Integer cont1 = SVQairport.genericalFlightCount(v-> v.getDestination.equals("Madrid")); System.out.println("The number of flights from Madrid is "+cont1);Date f = new Date(16,07,2014) Integer cont2 = SVQairport.genericalFlightCount(v->v.getDate().compareTo(f)>0); System.out.println("The number of flights later than 16th of July is "+cont2); 第1行:這是一個lambda表達式(請參閱下一部分),它表示作為參數傳遞的“條件”或“過濾器”表示每個Flight v返回有關其目的地是否為Madrid的條件
第6行:這是另一個lambda表達式,對于每個Flight v,如果出發日期晚于7月14日,則返回true
3. Lambda表達式
Java 8還有一點變化,那就是將功能接口作為方法的參數提供的方式。 如上例所示,為了在Java7中使用Comparator,定義了一個包含compare方法的外部類,并且該類的對象在調用時或通過先前創建的方法傳遞給需要它的方法賓語。 Java 8還有其他更靈活的機制來定義功能接口:lambda表達式和方法引用。
Lambda表達式是一種方法的簡化,其中輸入參數和輸出表達式由箭頭運算符'->'分隔。 輸入參數寫在方括號之間,并用逗號分隔。 如果接口具有單個輸入參數,則不需要括號。 因此,lambda表達式的第一部分的形狀將類似于()->如果沒有輸入參數,則x->如果只有一個或(x,y)->兩個參數。 通常,不必定義參數的類型,因為Java 8可以從上下文中推斷出它們。 在箭頭運算符->之后,我們必須編寫表達式,該表達式將成為我們聲明的接口的返回值。
示例1.接收航班并返回價格的功能接口為:
x-> x.getPrice();例子2.一個接收代表一個數字的String并返回一個帶有相應值的Integer的功能接口將寫為:
x -> new Integer(x);示例3.讓我們再舉一個接口Comparator的示例。 可以通過直接在調用代碼上使用lambda表達式構造所需的Comparator來執行對Collections的sort方法的調用:
Collections.sort(flightList,(x,y)->x.getPrice().compareTo(y.getPrice()));lambda表達式由其參數形成,在這種情況下為兩個:括號之間的x和y,并用逗號分隔。 有兩個參數,因為接口Comparator中的compare方法也需要兩個參數。 正如我們可以看到的,即使我們不需要正式指定x和y都是對Flight類型的引用,因為Java 8編譯器能夠從上下文中“理解”它,因為既然我們要訂購List <Flight> ,比較器必須為Flight類型,因此compare方法的參數也必須為該類型。 然后,在箭頭符號“->”之后,我們編寫表達式,compare方法應返回; 在我們的例子中,是一個類型為int的表達式,其中包含航班價格之間的比較。
引用比較器的另一種方法是調用返回我們要比較的屬性的方法。 例如,Java 8允許以下其他調用:
Collections.sort(flights,Comparator.comparing(Flight::getPrice));在第二次調用時,Comparator接口調用靜態方法compare,其參數是Function類型的功能接口,可以僅通過引用返回我們要比較的屬性的方法來定義它。
Lambda表達式最直接在需要功能接口的方法調用時直接使用,但是如果某個Lambda表達式將要使用多次,則可以使用標識符聲明它。
例。 為了定義一個航班是否滿載,我們將編寫:
Predicate<Flight> flightFull = x-> x.getNumPassengers().equals(x.getNumSeats());這樣,標識符flightFull可以在所有具有Predicate類型參數的調用上替換Predicate接口。
如果功能接口需要輸入的參數類型不同于為接口本身指定的類型,則更好的定義方法就好像它是方法一樣。
例。 如果我們需要定義一個飛行謂詞,以接收日期類型的參數,并告訴飛行是否在給定日期之后起飛,則我們定義:
Predicate<Flight> laterDate(Date f){return x -> x.getDate().compareTo(f)>0;}4.謂詞<T>
如前所述,謂詞接口為需要某種過濾器或條件的方法實現了邏輯條件。 謂詞實現了一種稱為test的方法,該方法從類型T的對象返回布爾值。因此,謂詞類型用于根據類型T的對象是否滿足特定屬性對其進行分類。 例如,給一個Flight告訴它是否完成,給一個Book告訴它的標題是否包含某個特定單詞,給一個Song告訴它是否持續超過x秒,或者給一個String告訴它是否以某個字符開頭。
示例1.判斷航班是否已滿的謂詞是:
Predicate<Flight> flightFull = x-> x.getNumPassengers().equals(x.getNumSeats());示例2.如果需要基于參數定義條件,則可以為功能接口提供輸入參數。 例如,如果我們需要確定某個航班是否是從確定的日期開始的,則可以定義:
Predicate<Flight> equalDate(Date f){return x -> x.getDate().equals(f);}此外,還可以使用專用接口(例如DoublePredicate,IntPredicate和LongPredicate)從基本數據類型的對象獲取邏輯值。
默認方法
謂詞接口還具有實現邏輯操作的三種方法:negate()和(Predicate)和or(Predicate)。 例如,如果我們需要Predicate類型的參數來判斷Flight是否對應于某個日期且已滿,則將其寫為:
equalDate(f).and(flightFull)5. BiPredicate <T,U>
BiPredicate接口從兩個不同類型的參數生成邏輯值。 例如,給定一個代表目的地和航班的字符串,它將返回該航班是否去往該目的地,給定一首歌曲和一段持續時間,它將判斷歌曲的持續時間是否小于指定的持續時間,等等。
例。 如果某個航班在指定日期起飛,則返回的界面為:
BiPredicate<Flight, Date> getCoincidence = (x,y)-> y.equals(x.getFecha());6.函數<T,R>
函數是帶有apply方法的接口,該接口接收T類型的參數并返回R類型的對象。它主要用于從另一種派生或組合的類型轉換對象。 例如,書籍的作者,歌曲的持續時間,航班的乘客人數等。Java 8提供了一組專用接口,這些接口的輸入或輸出參數類型不同。 例如,ToDoubleFunction <T>,ToIntFunction <T>,ToLongFunction <T>專門用于接收類型T的對象并返回接口名稱上指定的類型。 這些接口實現了一個稱為applyAsX的方法,其中X將根據情況區分為Double,Int或Long。 相反,函數LongFunction <R>,IntFunction <R>和DoubleFunction <R>接收名稱上指定的類型的值,并使用apply方法返回另一種類型R。 最后,還有六個名為XToYFunction的接口,其中X,Y取值Double,Int或Long,X為輸入參數類型,Y為返回值類型。 他們實現的方法是ApplyAsY,其中Y是返回值類型。
示例1:給定飛行確定其持續時間的功能可以定義為:
Function<Flight, Duration> functionDuration = x->x.getDuration();在這種情況下,如果Flight類型定義了getDuration方法,則可以在將使用Function類型作為輸入參數的方法的調用上使用運算符:::
Flight::getDuration當然,如果Function的表達式將不只使用一次,則lambda表達式x-> x.getDuration()可以作為需要此函數的方法的輸入參數。
示例2.給定一個航班,返回其占用率的函數為:
Function<Flight,Double>functOccRatio=x-> 1.*x.getNumPasengers()/x.getNumSeats();這種情況是專門功能的一個明顯例子:
ToDoubleFunction<Flight> functOccRatio(){return x->1.*x.getNumPassengers()/x.getNumSeats();}默認方法
Function接口有兩種方法,可以讓我們操作帶有組合的函數:compose(Function)和andThen(Function)。 它們之間的區別在于所涉及功能的應用順序。 應用方法f.compose(g)產生的函數首先應用g然后應用f,而f.andThen(g)是先應用f然后應用g的結果。
例。 讓我們假設我們有一個函數,給定類型為Duration的對象,它將返回其轉換為分鐘:
Function<Duration,Integer> inMinutes=x->x.getMinutes()+x.getHours()*60;另一個返回飛行時間的函數:
Function<Flight,Duration> getDuration = Flight::getDuration;然后,以分鐘為單位返回飛行時間的函數將是:
Function<Flight,Integer> getDurationInMinutes=inMinutes.compose(getDuration);或者:
Function<Flight,Integer> getDurationInMinutes =getDuration.andThen(inMinutes);7. BiFunction <T,U,R>
BiFunction是一個函數,該函數使用稱為apply的方法接收類型T和U的兩個參數并返回類型R的結果。 還有三個專門用于返回某種類型的接口:ToDoubleBiFunction,ToIntBiFunction和ToLongBiFunction,它們實現了applyAsX方法,其中X可以是Double,Int或Long。
例。 要獲得一個給定日期和航班的函數,該函數返回給定日期與航班起飛之間還剩下多少天,它是:
ToIntBiFunction<Flight, Date> getDays(Flight v, Date f){return (x,y)->y.subtract(x.getDate());}8.消費者<T>
接口Consumer是Function的變體,其中不返回任何值,這意味著它使用稱為accept的方法修改給定對象,該方法接收類型T的對象并返回void。 它們用于定義對對象的操作。 例如,將某個航班的價格增加一定的百分比,將“日期”減去幾天,或者在控制臺上打印一個值。 Java 8還提供了專門的接口DoubleConsumer,LongConsumer或IntConsumer,它們也實現了accept方法。
例子1.如果我們想將一個航班的價格提高10%,我們將定義一個消費者:
Consumer<Flight> incrementPrice10p = x->x.setPrice(x.getPrice()*1.1);示例2.如果我們希望在作為參數傳遞的百分比上執行增量,則可以為Flight類型編寫以下方法:
Consumer<Flight> incrementPrice(Double p){return x->x.setPrice(x.getPrice()*(1+p/100.)); }示例3。找到以下使用者來替換表達式System.out.println是非常常見的:
Consumer<Flight> printFlight = x->System.out.println(x);例子4.如果我們想要一種Flight方法,該方法可以根據條件對Flight類型的對象執行某種動作,我們可以這樣寫:
public void applyAction(Predicate<Flight> cond, Consumer<Flight> act){if (cond.test(this)){act.accept(this);}}一旦我們有了一個類型為Flight的對象v,則調用前一種方法來增加v的價格(如果乘客人數低于50)將是:
v.applyAction(x->x.getNumPassengers()<50, x->x.incrementPrice(10.));其中增量價格是示例2中定義的使用者。
9. BiConsumer <T,U>
BiConsumer是一個接口,用于定義對兩個不同類型的輸入參數的操作。 它用于表示修改接收另一個類型對象的對象的操作。 它的專用接口是:ObjDoubleConsumer,ObjIntConsumer和ObjLongConsumer,它們接收類型T的對象和名稱上指定的另一類型的對象。 它們都實現了一個稱為accept的功能方法。
例。 要更改飛行時間,我們可以編寫以下代碼:
BiConsumer<Flight, Duration> changeDuration = (x,y)->x.setDuration(y);10.供應商<T>
Supplier是一個接口,它使用稱為get的方法提供T類型的對象而沒有任何參數。 此外,還有專門的接口(例如BooleanSupplier,DoubleSupplier,IntSupplier和LongSupplier)提供指定類型的對象。 在這些情況下,他們實現的方法稱為getAsX,其中X分別為Boolean,Double,Int或Long。
通常,類型為Supplier的接口只會調用構造函數。 這樣,假設FlightImpl具有默認構造函數,則調用Flight構造函數的lambda表達式將是:
Supplier<Flight> giveMeFlight = ()-> new FlightImpl();如果我們希望供應商有一個爭論,我們將不得不寫:
Supplier<Flight> giveMeFlight (String s) {return ()->new FlightImpl(s);}建立供應商的另一種常用方法是使用方法表達式:
Supplier<Set<Integer>> giveMeSet = HashSet::new;11.一元運算符<T>
UnaryOperator接口表示一個操作,該操作使用apply方法接收一個類型T的單個參數并返回另一個相同類型的對象。 這是Function接口的一種特殊情況,其輸入和輸出值的類型相同,Java將其實現為Function的子接口。 Java 8還具有專門的接口DoubleUnaryOperator,IntUnaryOperator和LongUnaryOperator,它們分別實現方法applyAsX為X字符鏈Double,Int或Long。 由于此接口是Function的子接口,因此它也以相同的行為實現默認的方法compose和andThen。
示例1.如果我們需要一個運算符來修改Duration,并添加一個帶有參數的特定分鐘數,則可以這樣寫:
public UnaryOperator<Duration> addMinutes(Integer m){return x -> x.sum(new DurationImpl(0,m));}12. BinaryOperator <T>
接口BinaryOperator表示一個操作,該操作接收兩個類型T的操作數,并使用apply方法返回相同類型的結果。 如我們所見,這是BiFunction接口的一種特殊情況,其中三種類型T,U和R相同,Java 8將其實現為BiFunction的子接口。 還可以使用DoubleBinaryOperator,IntBinaryOperator和LongBinaryOperator等專業知識來操作數值。 在這些接口中,它們實現的方法是applyAsX ,其中X可以分別命名為Double,Int或Long。
示例1.我們定義了一個類型為Duration的類型,該類型存儲一個Flight的持續時間,以小時和分鐘為單位。 如果持續時間類型已經定義了sum方法:
public Duration sum(Duration d) {Integer min = getMinutes() + d.getMinutes();Integer hour = getHours() + d.getHours();return new DurationImpl(hour+min/60,min%60);}然后我們可以將其重新定義為BinaryOperator:
BinaryOperator<Duration> addDur = (x,y) -> x.sum(y);等效于其他表達式:
BinaryOperator<Duration> addDur = Duration::sum;如果未為Duration定義方法sum,我們可以直接定義:
BinaryOperator<Duration> addDur = (x,y)-> {Integer min = x.getMinutes() + y.getMinutes();Integer hour = x.getHours() + y.getHours();return new DurationImpl(hour+min/60,min%60);};示例2. DoubleBinaryOperator接口允許我們將實函數定義為其他兩個函數的組合。 例如,如果我們想將函數h定義為其他兩個未知函數f和g的商,我們將編寫代碼:
public DoubleBinaryOperator functionH(DoubleBinaryOperator f, DoubleBinaryOperator g){return (x,y)->f.applyAsDouble(x,y)/g.applyAsDouble(x,y);}這樣,兩個數字的加法與乘積之間的商的可能調用為:
public Double callFunctionH(Double x, Double y){return functionH((a,b)->a+b,(a,b)->a*b).applyAsDouble(x,y);}翻譯自: https://www.javacodegeeks.com/2015/03/functional-interfaces.html
總結
- 上一篇: 使用Curator和ZooKeeper发
- 下一篇: 楚乔传一共多少集 楚乔传简单介绍