Kotlin知識概括(十一) —— 高階函數

前序

      以前已經掌握了函數類型的定義以及lambda的使用,本次將完成高階函數與內聯函數的學習。java

高階函數就是以另外一函數做爲參數或返回值的函數。android

函數類型

函數類型的原理

      衆所周知,Kotlin是兼容Java 6的,但Java 6並無 lambda 。因此Kotlin會將一個函數類型的變量轉換爲一個FunctionN接口的實現。bash

      Kotlin標準庫中定義了一系列FunctionN接口,這些接口對應於不一樣參數數量的函數:Function0<R>表明沒有參數的函數,Function1<P1,R>表明一個參數的函數。N的取值範圍: 0 <= N <= 22,也就是說函數類型變量的參數最多22個。ide

public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}
複製代碼

      每個FunctionN接口都定義了一個invoke方法,一個函數類型的變量就是實現了對應FunctionN接口的實例,該 FunctionN接口的實例 invoke 方法中包含了函數類型變量的函數體。函數

函數類型實例的調用

      細心點會發現,每個FunctionN函數中的invoke()都帶有operator關鍵字修飾,這代表invoke()的調用能夠用操做符替代:post

表達式 轉換
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ……, i_n) a.invoke(i_1, ……, i_n)

函數類型變量可使用圓括號替代調用 invoke(),但仍是須要傳遞足夠數量的參數:性能

#daqiKotlin.kt
fun doSometing(func :() -> Unit){
    func()
    //func.invoke()
}
複製代碼

對於接收函數類型參數的函數,Java 8能夠直接使用lambda進行調用,而且lambda會被自動轉換成函數類型的值:學習

#daqiJava8.java
doSometing{
    
}
複製代碼

而對於Java 8如下的Java,須要按照函數類型的參數列表數量,選取適當的FunctionN接口,建立其匿名內部類。ui

#daqiJava.java
doSometing(new Function0<Unit>() {
    @Override
    public Unit invoke() {
        return Unit.INSTANCE;
    }
});
複製代碼

返回函數的函數

      函數類型能夠用做參數傳遞到函數中,也就意味着函數類型也能做爲返回值返回,雖然這並不經常使用。this

fun  doSometing():() -> Unit{
    return {
        
    }
}
複製代碼

內聯函數

lambda的開銷

      Kotlin的lambda帶來簡潔語法的同時,也帶來了必定的性能消耗。每個lambda表達式會被編譯成一個實現FunctionN接口的匿名類。

      對於捕捉變量的lambda表達式,每次調用都是建立一個新的對象,帶來額外的性能開銷。對於不捕捉變量的lambda表達式,只會建立一個單例的 FunctionN 實例,並在下次調用時複用。

      同時 Kotnlin 編譯出來的 Function 對象沒有避免對基本數據類型的裝箱和拆箱(由於接收的是Object類型)。這就意味着輸入值或輸出值涉及到基本數據類型時,會調用系統的裝箱與拆箱,形成必定的性能消耗。

(Function1)(new Function1() {

     public Object invoke(Object var1) {
        this.invoke(((Number)var1).intValue());
        return Unit.INSTANCE;
     }

     public final void invoke(int it) {
       //....
     }
})
複製代碼

      爲了生成與Java語句同樣高效的代碼,Kotlin提供了內聯機制。對於帶有inline修飾符函數,Kotlin編譯器會直接將函數實現的真實代碼替換每一次的函數被調用的地方。

      內聯的 lambda 表達式只能在內聯函數內部直接調用或者做爲可內聯的參數傳遞。不然,編譯器會禁止參數被內聯,並給出錯誤信息「Illegal usage of inline-parameter」。並且內聯的函數應儘可能簡單,好比Kotlin標準庫中的內聯函數老是很小的。

禁用內聯

      當一個lambda可能會包含不少代碼,或者以不容許內聯的方式使用,那麼能夠用 noinline 修飾符標記這些不但願內聯的函數類型參數:

inline fun daqi(noinline func :() -> Unit){
    func()
}
複製代碼

      若是一個內聯函數沒有可內聯的函數類型參數,編譯器會產生一個警告,由於這樣的內聯函數沒意義。noinline 修飾的函數類型參數能夠以任何方式操做。例如存儲在字段中等等。

集合的內聯操做

      集合的函數式Api操做都是內聯函數,因此它們的函數體都會被內聯,不會產生額外的類或者對象。

#_Collections.kt
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
複製代碼

      但序列的函數式Api中,中間操做並非內聯函數(末端操做是內聯函數),因此中間操做的lambda都會被保存在字段中,等待末端操做調用由中間操做組成的調用鏈。

#_Sequences.kt
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
    return FilteringSequence(this, true, predicate)
}

internal class FilteringSequence<T>(
    private val sequence: Sequence<T>,
    private val sendWhen: Boolean = true,
    private val predicate: (T) -> Boolean
) : Sequence<T> {
}
複製代碼

      因此,不該該老是試圖將集合轉化爲序列,只有在處理大量數據的集合時,才須要將集合轉換爲序列。對於小數據量的集合,仍是使用集合操做處理。

參考資料:

android Kotlin系列:

Kotlin知識概括(一) —— 基礎語法

Kotlin知識概括(二) —— 讓函數更好調用

Kotlin知識概括(三) —— 頂層成員與擴展

Kotlin知識概括(四) —— 接口和類

Kotlin知識概括(五) —— Lambda

Kotlin知識概括(六) —— 類型系統

Kotlin知識概括(七) —— 集合

Kotlin知識概括(八) —— 序列

Kotlin知識概括(九) —— 約定

Kotlin知識概括(十) —— 委託

Kotlin知識概括(十一) —— 高階函數

Kotlin知識概括(十二) —— 泛型

Kotlin知識概括(十三) —— 註解

Kotlin知識概括(十四) —— 反射

相關文章
相關標籤/搜索