2025年1月18日 星期六

JAVA 7 to 17 Lambda

一、前言

一、前言

返回目錄

Java 8 為了擺脫太囉嗦,匿名類別再進化,但其實是為了函數化(Functional),導入函數式程式設計(Functional programming, FP)理念。

數學與電腦科學中,Lambda的概念很早就提出過,直到解決邏輯一致性問題(簡單類型λ演算),才逐漸奠定λ演算在語言學和電腦科學學界擁有一席之地。

λ-演算結合了兩種簡化方式,使得這個語義變得簡單,變為可計算的函數,明確的計算模型。

第一種簡化是不給予函式一個確定名稱,而「匿名」地對待它們。例如,兩數的平方和函式

s q u a r e _ s u m ( x , y ) = x 2 + y 2 square\_sum(x, y)=x^2+y^2

可以用匿名的形式重新寫為:

( x , y ) x 2 + y 2 (x,y)\mapsto x^2+y^2

第二個簡化是λ演算只使用單一個參數輸入的函式。如果普通函式需要兩個參數,例如 s q u a r e _ s u m {\displaystyle square\_sum} 函式,可轉成接受單一參數,傳給另一個函式中介,而中介函式也只接受一個參數,最後輸出結果。例如,

( x , y ) x 2 + y 2 (x,y)\mapsto x^2+y^2

可以重新寫成:

x ( y x 2 + y 2 ) x\mapsto(y\mapsto x^2+y^2)

這是稱為柯里化的方法,對於柯里化轉換版的計算方式如下

( ( x , y ) x 2 + y 2 ) ( 3 , 2 ) = ( ( x ( y x 2 + y 2 ) ) ( 3 ) ) ( 2 ) = ( y 3 2 + y 2 ) ( 2 ) / / 在內層表達式中 x 的定義為 3 ,這就像 β 歸約一樣。 = 3 2 + 2 2 / / y 的定義為 2 ,再次如同 β 歸約。 = 13 \begin{split} \big((x,y)\mapsto x^2+y^2\big)(3,2)&=\Big(\big(x\mapsto(y\mapsto x^2+y^2)\big)(3)\Big)(2)\\ &=(y\mapsto 3^2+y^2)(2) \space //在內層表達式中{\displaystyle x}的定義為{\displaystyle 3},這就像β-歸約一樣。\\ &=3^2+2^2 \space //{\displaystyle y}的定義為{\displaystyle 2},再次如同β-歸約。\\ &=13 \end{split}

lambda演算中的所有函式都是匿名的,它們沒有名稱,它們只接受一個輸入變數,柯里化用於實現有多個輸入變數的函式。

目錄



二、完美的過渡至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.
規定只能有一個抽象方法,一山不容二虎。

FunctionalInterface Error
FunctionalInterface Error

讓我們使用該介面再次操作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個值。概念與數學函數雷同,例如 R = f ( T ) = T 2 + 5 cos ( T ) R=f(T)=T^2+5-\cos(T)
  • 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)

範例:

輸出結果:

沒有留言:

張貼留言