使用函式式介面

使用函式式介面

像上幾章提到的,函式式介面定義且只定義了一個抽象方法。函式式介面很有用,因為抽象方法的簽名可以描述Lambda表示式的簽名。函式式介面的抽象方法的簽名成為函式描述符。所以為了應用不同的Lambda表示式,你需要一套能夠描述常見函式描述符的函式式介面。Java API中已經有了幾個函式式介面,比如之前幾章提到的Comparator,Runnable和Callable。

Java8的庫設計師幫你在java。util。function包中引入了幾個新的函式式介面。我們接下來會介紹Predicate,Consumer和Function。

Predicate

java。util。function。Predicate介面定義了一個名叫test的抽象方法,它介紹範型T物件,並返回一個boolean。這恰恰和你先前建立的一樣,現在就可以直接使用了。在你需要表示一個涉及型別T的布林表示式時,就可以使用這個介面。比如,你可以定義一個接受String物件的Lambda表示式:

@FunctionalInterfacepublic interface Predicate{ boolean test(T t);}public static List filter(List list , Predicate

如果你去查Predicate介面的Javadoc說明,可能會注意到諸如and和or等其他方法。現在你不用太計較這些,我們會在後面討論。

Consumer

java。util。function。Consumer 定義了一個名叫accept的抽象方法,它接受泛型T的物件,沒有返回void。你如果需要訪問型別T的物件,並對其執行某些操作,就可以使用這個介面。比如,你可以用它來建立一個forEach方法,接受一個Intergers的列表,並對其中每個元素執行操作。在下面的程式碼中,你就可以使用這個forEach方法,並配合Lambda來列印列表中的所有元素。

@FunctionalalInterfacepublic interface Consumer{ void accept(T t);}public static void forEach(List list , Consumer c){ for(T i:list){ c。accept(i); }}forEach(Arrays。asList(1,2,3,4) , (Integer i) -> System。out。println(i));

Function

java。util。function。Function介面定義了一個叫做apply的方法,它接受一個泛型T的物件,並返回一個泛型R的物件。如果牛需要定義一個Lambda,將輸入物件的資訊對映到輸出,就可以使用這個介面(比如提取蘋果的重量,或把字串對映為它的長度)。在下面的程式碼中,會向你展示如何利用它來建立一個map方法,以將一個String列表對映到包含每個String長度的Integer列表。

@FunctionalInterfacepublic interface Function{ R apply(T t)}public static List map(List list , Function f){ List result = new ArrayList<>(); for(T s:list){ result。add(f。apply(s)); } return result;}List l = map(Arrays。asList(“guan,zhu,wo”),(S);

原始型別特化

我們介紹了三個泛型函式式介面:Predicate,Consumer和Function。還有些函式式介面專為某些型別而設計。

回顧一下:Java型別要麼是引用型別(比如Byte,Integer,Object,List),要麼是原始型別(int,double,byte,char)。但是泛型(比如Consumer 中的T)只能繫結應用型別。這是由泛型內部的實現方式造成的。因此,在Java裡有一個原始型別轉換為對應的引用型別的機制。這個機制叫做裝箱(boxing)。相反的操作,也就是將引用型別轉換為對應的原始型別,叫做拆箱(unboxing)。Java還有一個自動裝箱機制來幫助程式設計師執行這一個任務:裝箱和拆箱操作是自動完成的。比如,這就是為什麼下面的程式碼是有效的(一個int被裝箱成為Integer):

List list = new ArrayList<>();for(int i = 300 ; i < 400;i++){ list。add(i);}

但這在效能方面是要付出代價的。裝箱後的值本質就是把原始型別包裹起來,並儲存在堆裡面。因此,裝箱後的值需要更多的記憶體,並需要額外的記憶體搜尋來獲取被包裹餓的原始值。

Java 8為我們前面說的函式式介面帶來一個專門的版本,以便在輸入和輸出都是原始型別時避免自動裝箱的操作。比如,在下面的程式碼中,使用IntPredicate就避免了對值1000進行裝箱操作,但要是用Predicate就會把引數1000裝箱到一個Integer物件中:

public interface IntPredicate{ boolean test(int t);}IntPredicate evenNumbers = (int i) -> i % 2 == 0;evenNumbers。test(1000);Predicate oddNumbers = (Integer i) -> i % 2 == 1;oddNumbers。test(1000);

一般來說,針對專門的輸入引數型別的函式式介面的名稱都要加上對應的原始型別字首,比如D歐巴了 Predicate,IntConsumer,LongBinaryOperator等。Function介面還有針對輸出引數的變種:ToIntFunction,IntToDoubleFunction等。

使用函式式介面

為了總結關於函式式介面和Lambda的討論,下面總結了一些使用案例、Lambda的例子,以及可以使用的函式式介面。

使用函式式介面

請注意,任何函式式介面都不允許丟擲受檢異常。如果你需要Lambda表示式來丟擲異常,有兩種方法:定義一個自己的函式式介面,並且宣告受檢異常,或者把Lambda包在一個try/catch塊中。

現在你知道如何建立Lambda,在哪裡以及如何使用他們了。後面的章節我們會介紹一些更高階的細節:編譯器如何對Lambda做型別檢查,以及你應當瞭解的規則,諸如Lambda在自身內部引用區域性變數,還有和void相容的Lambda等。