一、前言
Java 8 為了擺脫太囉嗦,匿名類別再進化,但其實是為了函數化(Functional),導入函數式程式設計(Functional programming, FP)理念。
數學與電腦科學中,Lambda的概念很早就提出過,直到解決邏輯一致性問題(簡單類型λ演算),才逐漸奠定λ演算在語言學和電腦科學學界擁有一席之地。
λ-演算結合了兩種簡化方式,使得這個語義變得簡單,變為可計算的函數,明確的計算模型。
第一種簡化是不給予函式一個確定名稱,而「匿名」地對待它們。例如,兩數的平方和函式
可以用匿名的形式重新寫為:
第二個簡化是λ演算只使用單一個參數輸入的函式。如果普通函式需要兩個參數,例如
可以重新寫成:
這是稱為柯里化的方法,對於柯里化轉換版的計算方式如下
lambda演算中的所有函式都是匿名的,它們沒有名稱,它們只接受一個輸入變數,柯里化用於實現有多個輸入變數的函式。
目錄
- 一、前言
- 二、完美的過渡至Lambda
- 三、自定義函數化Interface
- 四、Java的規劃:java.util.function
- 五、Consumer Interface
- 六、Supplier Interface
- 七、Function Interface
- 八、Predicate Interface
- 九、方法引用(Method Reference)
二、完美的過渡至Lambda
我們大約已經知道Lambda演算就是函數匿名化且可以處理多個輸入變數,語法定義如下
結合程式語言函數內可以處理更複雜的運算(多行),使用大括號
{}
形成一個程式區塊,就可以執行複雜的程序了!
可是Java是物件導向,所有的建制單位都是Class(基本型別除外)為主,那要怎麼導入函數化呢?
我們知道Lambda表達式是直接表示一個匿名函數,參數(parameter)與實作方法(expression, code block)都在這個Lambda表示完成。聰明的您應該覺得很熟悉,這不就是覆蓋(Override)一個方法的實作流程!但Lambda沒有名字,覆蓋操作需要實作(implements)或繼承(extends)且要有方法名,等等...,直接使用匿名類別就好了,型別推斷為宣告的型別,可是...沒有方法名...,那還不簡單,推斷的型別如果只有一個方法...,別無選擇,簡單又粗暴!
我們先來看一下常用的Runnable,非常符合導入Lambda:
匿名類別的寫法:
只留下要實作的參數數量與覆蓋實作方法,調整成Lambda表達式:
執行看看結果:
Lambda效果良好,而且不會產生匿名的內部類別(inner class)檔案Lambda17$1.class,Class檔變得很乾淨。
三、自定義函數化Interface
如果只有Lambda還真的不夠看,原生的Java中能夠直接利用的,似乎不多(如果大大知道的可以再跟我說),如Runnable、Callable、Comparable...。所以Java提供新註解
@FunctionalInterface
,讓您可以客製化一個抽象方法(single-abstract-method)的介面(interface)。
使用
@FunctionalInterface
有以下這些好處:
(3.1) 利用註解可告知開發人員此介面是函數介面(功能介面),以下稱為函數介面,裡面只有一個方法需要實作,可搭配Lambda表達式使用。
(3.2) 在規劃階段,已經定義為函數介面,後續在開發維護時,可以讓編譯器直接檢查是否符合函數介面的限制條件,不符合編譯時直接報錯,以符合分析規劃等規範。例如以下的code就會報錯
錯誤訊息:Invalid '@FunctionalInterface' annotation; ICustFunctional is not a functional interface.
規定只能有一個抽象方法,一山不容二虎。


讓我們使用該介面再次操作Lambda看看
執行結果:
四、Java的規劃:java.util.function
Java團隊已經幫各位想好大多數的情境下要如何使用Lambda,定義許多的函數介面在java.util.function套件中,我們就不用在費盡心思要怎麼規劃與命名,真的是太棒了!
Java17 API 網站(java.util.function)
數字類型:提供dobule, int, long, object等相關操作。
- DoubleBinaryOperator
- DoubleConsumer
- DoubleFunction
- DoublePredicate
- DoubleSupplier
- DoubleToIntFunction
- DoubleToLongFunction
- DoubleUnaryOperator
- IntBinaryOperator
- IntConsumer
- IntFunction<R>
- IntPredicate
- IntSupplier
- IntToDoubleFunction
- IntToLongFunction
- IntUnaryOperator
- LongBinaryOperator
- LongConsumer
- LongFunction<R>
- LongPredicate
- LongSupplier
- LongToDoubleFunction
- LongToIntFunction
- LongUnaryOperator
- ObjDoubleConsumer<T>
- ObjIntConsumer<T>
- ObjLongConsumer<T>
- ToDoubleBiFunction<T,U>
- ToDoubleFunction<T>
- ToIntBiFunction<T,U>
- ToIntFunction<T>
- ToLongBiFunction<T,U>
- ToLongFunction<T>
邏輯處理與控制:
- BiConsumer<T,U>
- BiFunction<T,U,R>
- BinaryOperator<T>
- BiPredicate<T,U>
- BooleanSupplier
- Consumer<T>
- Function<T,R>
- Predicate<T>
- Supplier<T>
- UnaryOperator<T>
命名規則解析如下:
- Consumer: 消費者,接收1個參數,無回傳值。概念與POJO的setter雷同。
- Supplier: 提供者,無法接收參數,回傳1個值。概念與POJO的getter雷同。
-
Function: 函數,接收1個參數,回傳1個值。概念與數學函數雷同,例如
- Predicate: 決策(斷定),接收1個參數,回傳boolean值,決定Yes or No。
- Unary: 單一型別(一元運算),輸入輸出型別相同。
五、Consumer Interface
Java 中的 Consumer 是一個函數介面,它可以接受一個泛型 <T> 類型參數,進行處理後無任何返回值。例如傳入一個字串,印出一個字串。
(5.1) Consumer accept
void accept(T t)
是函數介面的抽象方法,傳入一個任意類型,無回傳值,可以用於 Lambda 表達式和方法參考。
範例:輸出字串
輸出結果:
(5.2) Consumer andThen
default Consumer<T> andThen(Consumer<? super T> after)
是Consumer的預設方法,可以傳入一個 Consumer,傳回處理後的Consumer後的 Consumer ,傳入的 Consumer 不能為 null,否則會得到 NullPointerException。
範例:先輸出字串長度,再輸出字串
輸出結果:
(5.3) Iterable forEach
default void forEach(Consumer<? super T> action)
是Iterable的預設方法,傳入一個Consumer,再使用for迴圈一個一個處理。
Iterable原始碼:
範例:建立一個List,輸出所有元素
輸出結果:
(5.4) 其他與Consumer相關
Interface | Description |
---|---|
BiConsumer<T,U> | 傳入兩個任意型別參數,無回傳值 |
DoubleConsumer | 傳入一個 double 參數,無回傳值 |
IntConsumer | 傳入一個 int 參數,無回傳值 |
LongConsumer | 傳入一個 long 參數,無回傳值 |
ObjDoubleConsumer<T> | 傳入一個任意型別參數,一個 double 參數,無回傳值 |
ObjIntConsumer<T> | 傳入一個任意型別參數,一個 int 參數,無回傳值 |
ObjLongConsumer<T> | 傳入一個任意型別參數,一個 long 參數,無回傳值 |
範例:使用ObjIntConsumer<T>判斷集合中數值大於8的元素輸出出來
輸出結果:
六、Supplier Interface
Java 中的 Supplier 是一個函數介面,無參數,傳回值類型為泛型 T。 Supplier 的使用比較簡單,使用場景也比較單一。
(6.1) Supplier get
T get()
是Supplier的抽象方法,無參數,傳回值類型為泛型 T。可以用此方法取得邏輯、演算法一致的資料或創建實例(Instance)。
範例:連續隨機3個1~6整數,輸出兩次,分別代表兩個玩家
輸出結果
(6.2) 其他與Supplier相關
Interface | Description |
---|---|
BooleanSupplier | 無參數,回傳一個布林值。 |
DoubleSupplier | 無參數,回傳一個雙精度浮點數。 |
IntSupplier | 無參數,回傳一個整數。 |
LongSupplier | 無參數,回傳一個長整數。 |
範例:設定一個根號2的值
輸出結果
七、Function Interface
在D:\jdk\zulu17.54.21-ca-dk17.0.13-win_x64\bin\java 中,Function 是一個函數介面,它可以接受一個泛型 T 對象,傳回一個泛型 R 對象,即參數類型和返回類型可以不同。
(7.1) Function apply
R apply(T t)
是 Function 的抽象方法,接受一個泛型 T 對象,傳回一個泛型 R 對象。可針對給予的資料,進行分析、邏輯判斷、資料處理查詢儲存等功能,最後返回值提供使用與參考。
範例:輸入字串 <T> String,傳回字串的大寫形式 <R> String。
輸出結果:
(7.2) Function andThen
default <V> Function<T,V> andThen(Function<? super R,? extends V> after)
是 Function 的預設方法,傳回一個組合函數,該函數首先將此函數應用於其輸入,然後將 after 函數應用於結果。
範例:輸入一個字串,取得字串的長度,然後乘以2。
輸出結果:
(7.3) Function compose
default <V> Function<V,R> compose(Function<? super V,? extends T> before)
是 Function 的一個預設方法,傳回一個組合函數,該函數首先將 before 函數應用於其輸入,然後將此函數應用於結果。順序與andThen相反。
範例:輸入一個字串,取得字串的長度,然後乘以2。
輸出結果:
(7.4) Function identity
static <T> Function<T,T> identity()
是 Function 的一個靜態方法,回傳一個Function,其輸出值等於輸入值。常用於數據串接。
範例:
輸出結果:
(7.5) 其他與Function相關
Interface | Description |
---|---|
BiFunction<T,U,R> | 輸入2個泛型<T>,<U>參數,回傳1個泛型<R> |
DoubleFunction<R> | 輸入1個double,回傳1個泛型<R> |
DoubleToIntFunction | 輸入1個double,回傳1個int |
DoubleToLongFunction | 輸入1個double,回傳1個long |
IntFunction<R> | 輸入1個int,回傳1個泛型<R> |
IntToDoubleFunction | 輸入1個int,回傳1個double |
IntToLongFunction | 輸入1個int,回傳1個long |
LongFunction<R> | 輸入1個long,回傳1個泛型<R> |
LongToDoubleFunction | 輸入1個long,回傳1個double |
LongToIntFunction | 輸入1個long,回傳1個int |
ToDoubleBiFunction<T,U> | 輸入2個泛型<T>,<U>參數,回傳1個double |
ToDoubleFunction<T> | 輸入1個泛型<T>參數,回傳1個double |
ToIntBiFunction<T,U> | 輸入2個泛型<T>,<U>參數,回傳1個int |
ToIntFunction<T> | 輸入1個泛型<T>參數,回傳1個int |
ToLongBiFunction<T,U> | 輸入2個泛型<T>,<U>參數,回傳1個long |
ToLongFunction<T> | 輸入1個泛型<T>參數,回傳1個long |
八、Predicate Interface
Predicate函數介面,它可以接受一個泛型 <T> 參數,返回值為布林類型元素。
(8.1) Predicate test
boolean test(T t)
是 Predicate 的抽象函數,輸入泛型 <T> 參數,輸出布林值,用於判斷一個參數是否滿足某個條件。
輸出:判斷某個字串是否為空。
輸出結果:
(8.2) Predicate and
default Predicate<T> and(Predicate<? super T> other)
是 Predicate 的一個預設方法,使用and()方法,可以讓前後兩個Predicate判斷條件同時生效。
範例: 判斷數字大小是否在 5 至 9 之間的數字。
輸出結果:
(8.3) Predicate negate
default Predicate<T> negate()
是 Predicate 的一個預設方法,傳回一個與指定判斷相反的Predicate。
範例:判斷數字不大於5的數字。
輸出結果:
(8.4) Predicate or
default Predicate<T> or(Predicate<? super T> other)
是 Predicate 的一個預設方法,使用or()方法,判斷前後兩個Predicate條件都不成立回傳false,至少一個成立回傳ture。
範例:判斷數字小於等於5,或大於等於9的數字。
輸出結果:
(8.5) Predicate not
static <T> Predicate<T> not(Predicate<? super T> target)
是 Predicate 的一個靜態方法,傳回一個與
傳入Predicate相反的結果。
範例:判斷不小於等於5的數字
輸出結果:
(8.6) Predicate isEqual
static <T> Predicate<T> isEqual(Object targetRef)
是 Predicate 的一個靜態方法,傳回一個Predicate,用來測試Object是否相同,測試條件等同Objects.equals(Object, Object)。
範例:判斷Integer是否相等
輸出結果:
(8.7) 其他與Predicate相關
Interface | Description |
---|---|
BiPredicate<T,U> | 輸入2個泛型<T>,<U>參數,回傳1個boolean |
DoublePredicate | 輸入1個double,回傳1個boolean |
IntPredicate | 輸入1個int,回傳1個boolean |
LongPredicate | 輸入1個long,回傳1個boolean |
九、方法引用(Method Reference)
方法引用是一種特殊型別的 lambda 表達式。使用雙冒號
::
(double colon)區隔函數(method),例如
System.out::println
。
您可以使用 lambda 表達式來建立匿名方法。然而,有時 lambda 表達式除了呼叫現有方法之外什麼也不做。在這些情況下,透過名稱引用現有方法通常會更清楚。方法引用使您能夠做到這一點;它們是緊湊、易於閱讀的 lambda 表達式,適用於已經有名稱的方法。
(9.1) 靜態方法(Static Method)
(9.2) 物件的實例方法(an Instance Method of a Object)
(9.3) 建構子(Constructor)
範例:
輸出結果:
沒有留言:
張貼留言