kotlin學(xué)習(xí)總結(jié)之五 進(jìn)階函數(shù)

一. 一等公民函數(shù)

在程序世界里,有且不僅有這么幾種權(quán)力:創(chuàng)建,賦值,傳遞。在JAVA中這些權(quán)力,object 都具備,function 都不具備。object 可以通過參數(shù)傳遞到另一個(gè)對象里,從而兩個(gè)對象可以互相通信。函數(shù)卻不行,兩個(gè)函數(shù)想要通信,必須以對象為介質(zhì)

以 Java 舉個(gè)例子:函數(shù)a,想要調(diào)用函數(shù)b。雖然a并不關(guān)心函數(shù)b是從哪兒來的,只要函數(shù)b可以完成這個(gè)特定的功能即可。但是在 Java 的世界里函數(shù)必須要依附在一個(gè)對象上,所以函數(shù)a必須依附在對象A上,函數(shù)b必須依附在對象B上,函數(shù)a必須通過一個(gè)對象才能找到函數(shù)b,如下:

public class A {
    public void a(Object o) {
        System.out.println("a is invoked");
        o.getClass().getMethod("b").invoke(o);
   }
}

public class B {
    public void b() {
        System.out.println("b is invoked");
    }
}

函數(shù)b可以這樣傳遞給函數(shù)a:

new A().a(new B());

結(jié)果如下:

a is invoked
b is invoked

通過這個(gè)簡單的例子,可以看出,非一等公民的函數(shù)生存條件有多么的惡劣,通訊的阻力有多大。

在函數(shù)是一等公民的世界里,函數(shù)a可以不再依附于對象A而單獨(dú)存在,函數(shù)a可以直接與函數(shù)b交流,不再需要通過對象才能找到函數(shù)b。

函數(shù)是"一等公民"特點(diǎn)指的就是函數(shù)與變量、對象類型一樣,處于平等地位。一等公民函數(shù)有三個(gè)主要的特點(diǎn)。

  • 函數(shù)可以賦值給一個(gè)變量。
  • 函數(shù)可以作為參數(shù)傳入另一個(gè)函數(shù)。
  • 函數(shù)可以作為別的函數(shù)的返回值。

1. 函數(shù)可以賦值給一個(gè)變量

既然函數(shù)可以賦值給一個(gè)變量,那么這個(gè)變量的類型就是函數(shù)類型。Kotlin 中每一個(gè)函數(shù)都有一個(gè)類型,稱為 “函數(shù)類型”,函數(shù)類型是一種數(shù)據(jù)類型,它與 Int、Boolean等數(shù)據(jù)類型 在使用場景上沒有區(qū)別?!?:” 可以取出函數(shù)的地址引用。
例如:

// 計(jì)算一個(gè)矩形面積  (Double, Double) -> Double
fun rectangleArea(width: Double, height: Double): Double {
    return width * height
}

fun main(args: Array<String>) {
//   通過::取出rectangleArea函數(shù)的地址 將函數(shù)rectangleArea賦值給一個(gè)變量areaFunction,
//   此時(shí)areaFunction變量的類型為(Double, Double) -> Double
    val areaFunction: (Double, Double) -> Double = ::rectangleArea
    //靠變量來調(diào)用函數(shù)
    val area = areaFunction(50.0, 40.0)
    println(area)   // 2000.0
}

一些相對簡單的函數(shù)類型:

//無參、無返回值的函數(shù)類型(Unit 返回類型不可省略)
() -> Unit
//接收T類型參數(shù)、無返回值的函數(shù)類型
(T) -> Unit
//接收T類型和A類型參數(shù)、無返回值的函數(shù)類型(多個(gè)參數(shù)同理)
(T,A) -> Unit
//接收T類型參數(shù),并且返回R類型值的函數(shù)類型
(T) -> R
//接收T類型和A類型參數(shù)、并且返回R類型值的函數(shù)類型(多個(gè)參數(shù)同理)
(T,A) -> R

2. 函數(shù)作為參數(shù)

函數(shù)可以作為參數(shù)進(jìn)行傳遞,如果函數(shù)可以作為參數(shù)進(jìn)行傳遞,那么就可以將不同函數(shù)進(jìn)行組合,提高代碼的復(fù)用,代碼會(huì)更簡潔,這部分就可以引出高階函數(shù),類似f(g(x))的形式。

// 計(jì)算一個(gè)矩形面積  (Double, Double) -> Double
fun rectangleArea(width: Double, height: Double): Double {
    return width * height
}

//計(jì)算一個(gè)三角形面積
fun triangleArea(bottom: Double, height: Double): Double {
    return (bottom * height) / 2
}

//獲取面積
fun getAreaByFun(funName: (Double, Double) -> Double, a: Double, b: Double): Double {
    return funName(a, b)
}

fun main(args: Array<String>) {
    //參數(shù)為函數(shù),傳入不同的函數(shù)類型
    var triangleArea: Double = getAreaByFun(::triangleArea, 10.0, 15.0)
    print(triangleArea)
    var rectangleArea = getAreaByFun(::rectangleArea, 10.0, 15.0)
    print(rectangleArea)
}

3. 函數(shù)可以作為別的函數(shù)的返回值。

函數(shù)可以作為返回值,那么函數(shù)內(nèi)應(yīng)該可以定義函數(shù),并且函數(shù)可以返回函數(shù)內(nèi)定義的函數(shù)。

//獲取面積,返回值是一個(gè)函數(shù)的類型
fun getArea(type: String): (Double, Double) -> Double {
    val resultFunction: (Double, Double) -> Double
    if (type == "rectangle") {
        resultFunction = ::rectangleArea
    } else {
        resultFunction = ::triangleArea
    }
    return resultFunction
}

fun main(args: Array<String>) {
    //調(diào)用函數(shù)
    val rectangleAreaFun: (Double, Double) -> Double = getArea("rectangle")
    println("底 10 高 15,計(jì)算三角形面積:${rectangleAreaFun(10.0, 15.0)}")
    //調(diào)用函數(shù)
    val triangleAreaFun: (Double, Double) -> Double = getArea("triangle")
    println("底 10 高 15,計(jì)算長方形面積:${triangleAreaFun(10.0, 15.0)}")
}

二. 函數(shù)式編程

函數(shù)是“一等公民”是函數(shù)式編程的核心概念。

  • 使用表達(dá)式,不用語句:函數(shù)式編程關(guān)心輸入和輸出,即參數(shù)和返回值。在程序中使用表達(dá)式可以有返回值,而語句沒有。例如控制結(jié)構(gòu)中的 if 和 when 結(jié)構(gòu)都屬于表達(dá)式。

  • 高階函數(shù):函數(shù)式編程支持高階函數(shù),所謂的高階函數(shù)就是一個(gè)函數(shù)可以作為另一個(gè)函數(shù)的參數(shù)或返回值。

  • 無副作用:是指函數(shù)執(zhí)行過程會(huì)返回同一個(gè)結(jié)果,不會(huì)修改外部變量,這就是“純函數(shù)”,同樣的輸入?yún)?shù)一定會(huì)有同樣的輸出結(jié)果。

Kotlin 語言支持函數(shù)式編程,提供了函數(shù)類型、高階函數(shù) 和 Lambda 表達(dá)式。

四. 匿名函數(shù)

匿名函數(shù)就是沒有名字的函數(shù)對象(注意匿名函數(shù)不是函數(shù),而是函數(shù)類型的對象),大多數(shù)情況下我們定義的函數(shù)都是具名函數(shù)(有名字的函數(shù))。匿名函數(shù)就是只定義參數(shù)列表、返回值類型和函數(shù)體,把一個(gè)匿名函數(shù)賦給一個(gè)沒有定義函數(shù)體的函數(shù)對象。那這種沒有匿名函數(shù)我們怎么調(diào)用呢?答案是無法直接調(diào)用。匿名函數(shù)可以賦值給一個(gè)變量,或者當(dāng)作實(shí)參直接傳遞給一個(gè)函數(shù)類型的形參。

具名函數(shù)如下:

fun sum(arg1 : Int,arg2 : Int): Int{
    return arg1 + arg2
}

這個(gè)函數(shù)的名字就叫sum。
那匿名函數(shù)定義:

fun(arg1 : Int, arg2 : Int) : Int{
    return arg1 + arg2
}

這樣寫還不行,因?yàn)閴焊恢朗裁磿r(shí)候用,所以我們需要付給一個(gè)引用,用來保存它,然后在需要使用的時(shí)候調(diào)用:

val sum = fun(arg1 : Int, arg2 : Int) : Int{
    return arg1 + arg2
}

三. lambda表達(dá)式

1. 定義

Lambda 表達(dá)式的本質(zhì)其實(shí)就是匿名函數(shù)。而函數(shù)其實(shí)就是功能(function),匿名函數(shù),就是匿名的功能代碼了。Lambda表達(dá)式才是與高階函數(shù)的絕配,平時(shí)我們給高階函數(shù)中的函數(shù)類型參數(shù)傳遞值時(shí),一般都會(huì)選擇傳入Lambda表達(dá)式,因?yàn)樗銐蚝啙嵟c強(qiáng)大。

Lambda表達(dá)式的本質(zhì)是匿名函數(shù),而匿名函數(shù)的本質(zhì)是函數(shù)類型的對象。因此,Lambda表達(dá)式、匿名函數(shù)、雙冒號+函數(shù)名這三個(gè)東西,都是函數(shù)類型的對象,他們都能夠賦值給變量以及當(dāng)作函數(shù)的參數(shù)傳遞!

創(chuàng)建一個(gè)函數(shù)類型的對象(函數(shù)字面量)有三種方式:

  • 函數(shù)引用,::函數(shù)名,表示函數(shù)引用,會(huì)拿到一個(gè) 函數(shù)的對象 ;注意不是函數(shù)本身。
  • 匿名函數(shù),沒有名字的函數(shù)類型的對象。
  • lambda是匿名函數(shù)的表現(xiàn)形式也就同上。

通常這樣寫匿名函數(shù):

val addFun = fun(x: Int, y: Int): Int {
    return x + y
}

使用lambda表達(dá)式可以簡化:

//lambda表達(dá)式
val addLambda = { x: Int, y: Int -> x * y }

2. 語法

  • 總是被大括號擴(kuò)著
  • 其參數(shù)(如果存在)在->之前聲明(參數(shù)類型可以省略)
  • 函數(shù)體(如果存在)在->后面
  1. 無參數(shù)的情況
    val/var 變量名 = { 操作代碼 }
val sum = { }
  1. 有參數(shù)的情況
    val/var 變量名:(參數(shù)類型,參數(shù)類型,...)->返回值類型 = (參數(shù)1,參數(shù)2,...->操作參數(shù)的代碼)
val sum:(Int,Int)->Int = {a,b->a+b}

可等價(jià)于
//此種寫法:即表達(dá)式的返回值類型會(huì)根據(jù)操作代碼自推導(dǎo)出來
val/var 變量名 = {參數(shù)1:類型,參數(shù)2:類型...->操作代碼}

val sum={a:Int,b:Int ->a+b}

3.lambda表達(dá)式作為函數(shù)中的參數(shù)的時(shí)候
fun sum(a:Int,參數(shù)名:(參數(shù)1:類型,參數(shù)2:類型...)->表達(dá)式返回類型){ ... }

簡化寫法
當(dāng) lambda 表達(dá)式只接受一個(gè)參數(shù)時(shí),該參數(shù)可以省略,使用時(shí)用 it 來表示該參數(shù):

add("xxx", { s -> s + "xxx" })
//等同于
add("xxx", { it + "xxx" })復(fù)制代碼

當(dāng)函數(shù)最后一個(gè)參數(shù)為函數(shù)時(shí),該函數(shù)可以寫在 () 外,并用 {} 包裹

add("xxx", { s -> s + "xxx" })
//等同于
add("xxx") { s -> s + "xxx" }
//等同于
foo("xxx") { it + "xxx" }復(fù)制代碼

當(dāng)函數(shù)只有一個(gè)參數(shù),且該參數(shù)為函數(shù)時(shí),可以直接省去 ()

foo({ s -> s + "xxx" }) 
//等同于
foo { s -> s + "xxx" }復(fù)制代碼

當(dāng)參數(shù)在函數(shù)體中沒有引用時(shí),可以將其設(shè)為 _,若此時(shí)只有一個(gè)參數(shù)(且該參數(shù)沒有被引用),則可以直接省略該參數(shù);若有兩個(gè)或以上的參數(shù),就算全部都沒有被引用,也不可以省略

foo({ s -> print("xxx") })
//等同于
foo({ _ -> print("xxx") })
//等同于
foo({ print("xxx") })
//等同于
foo { print("xxx") }

3. 返回值

lambda表達(dá)式返回值總是返回函數(shù)體內(nèi)部最后一行表達(dá)式的值。
lambda表達(dá)式語法缺少指定函數(shù)的返回類型的能力,因此Lambda表達(dá)式不能指定返回值類型,當(dāng)需要顯式指定返回類型時(shí),可以使用匿名函數(shù)。

fun(x: Int, y: Int): Int {
    return x + y
}

4. 帶接收者的Lambda

目前講到的lambda都是普通lambda,lambda中還有一種類型:帶接收者的lambda。
帶接受者的lambda的類型定義:

A.() -> C 

表示可以在A類型的接收者對象上調(diào)用并返回一個(gè)C類型值的函數(shù)。

帶接收者的lambda好處是,在lambda函數(shù)體可以無需任何額外的限定符的情況下,直接使用接收者對象的成員(屬性或方法),亦可使用this訪問接收者對象。

Kotlin的標(biāo)準(zhǔn)庫中就有提供帶接收者的lambda表達(dá)式:with和apply。

在kotlin中,提供了指定的接受者對象調(diào)用Lambda表達(dá)式的功能。在函數(shù)字面值的函數(shù)體中,可以調(diào)用該接收者對象上的方法而無需任何額外的限定符。它類似于擴(kuò)展函數(shù),它允許在函數(shù)體內(nèi)訪問接收者對象的成員。

val iop = fun Int.( other : Int) : Int = this + other
println(2.iop(3))
結(jié)果為5

5. 在android使用例子

Java8 開始支持 Lambda 表達(dá)式
Java 在使用 單 抽象方法的接口時(shí),允許使用 lambda 表達(dá)式

在 Kotlin 中就不支持這么寫了,因?yàn)闆]有必要(可以直接傳函數(shù)對象)

但在 Kotlin 和 Java 做交互的時(shí)候可以這么寫。

首先來通過一個(gè)例子直觀感受一下lambda表達(dá)式。Android開發(fā)中經(jīng)常會(huì)給一個(gè)Button設(shè)置OnClickListener。比如我們需要讓按鈕點(diǎn)擊后消失,平時(shí)我們可能是這樣寫的:

//傳統(tǒng)Java式寫法
mButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            view.setVisibility(View.GONE);
        }
    });

而在Kotlin中,使用函數(shù)式語法,我們可以這樣寫:

//Kotlin函數(shù)式寫法
mButton.setOnClickListener { 
    it.visibility = View.GONE
}

直觀來講,似乎跟我們平時(shí)的寫法差別有點(diǎn)大,比如,函數(shù)調(diào)用的小括號不見了,匿名內(nèi)部類直接被一個(gè)函數(shù)體取代了,View參數(shù)不見了,分號也消失了,還有那個(gè)it是什么……其實(shí),就像數(shù)學(xué)公式推導(dǎo)一樣,精簡的寫法也是通過一步一步簡化來的。下面就讓我們來看一下代碼段2的“推導(dǎo)過程”:

  1. 首先,代碼段1轉(zhuǎn)換為Kotlin代碼,并替換為函數(shù)式寫法:
mButton.setOnClickListener({ view: View ->
    view.visibility = View.GONE
})

這段代碼非常清晰,花括號包裹的是一段lambda表達(dá)式,可以把它作為實(shí)參傳遞給函數(shù),這一步把匿名內(nèi)部類省略掉了;另外也干掉了分號,因?yàn)樵贙otlin中行末尾的分號可以省略;最后還省略了set方法,在Kotlin中,會(huì)默認(rèn)把對屬性的直接訪問轉(zhuǎn)換成get/set方法調(diào)用。

  1. 然后,根據(jù)Kotlin的語法約定,如果lambda表達(dá)式是函數(shù)調(diào)用的最后一個(gè)實(shí)參,就可以把它挪到小括號外面:
mButton.setOnClickListener() { view: View ->
    view.visibility = View.GONE
}
  1. 當(dāng)lambda是函數(shù)的唯一實(shí)參,就可以去掉空的小括號對:
mButton.setOnClickListener { view: View ->
    view.visibility = View.GONE
}
  1. 如果lambda的參數(shù)的類型可以被編譯器推導(dǎo)出來,就可以省略它:
mButton.setOnClickListener { view ->
    view.visibility = View.GONE
}
  1. 最后,如果這個(gè)lambda只有一個(gè)參數(shù),并且這個(gè)參數(shù)的類型可以被推斷出來(也就是同時(shí)滿足3和4),那么這個(gè)參數(shù)也可以省略掉。代碼中引用這個(gè)參數(shù)的地方可以通過編譯器自動(dòng)生成的名稱it來替代:
mButton.setOnClickListener {
    it.visibility = View.GONE
}

經(jīng)過上述5個(gè)步驟,就得到了最簡潔、最清晰的代碼段。

四. 閉包

1. 定義

我們都知道,程序的變量分為全局變量和局部變量,全局變量,顧名思義,其作用域是當(dāng)前文件甚至文件外的所有地方;而局部變量,我們只能再其有限的作用域里獲取。
那么,如何在外部調(diào)用局部變量呢?答案就是——閉包,與此給閉包下個(gè)定義:閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)
閉包,即是函數(shù)中包含函數(shù),這里的函數(shù)我們可以包含(Lambda表達(dá)式,匿名函數(shù),局部函數(shù),對象表達(dá)式)。

fun test1(){
     fun test2(){
  }
}

2. 閉包使用

我們來看一個(gè)閉包的例子:

fun returnFun(): () -> Int {
    var count = 0
    return { count++ }
}

fun main() {
    val function = returnFun()
    val function2 = returnFun()
    println(function()) // 0
    println(function()) // 1
    println(function()) // 2

    println(function2()) // 0
    println(function2()) // 1
    println(function2()) // 2
}

returnFun返回了一個(gè)函數(shù),這個(gè)函數(shù)沒有入?yún)?,返回值是Int。我們可以用變量接收它,還可以調(diào)用它。function和function2分別是創(chuàng)建的兩個(gè)函數(shù)實(shí)例。
每調(diào)用一次function(),count都會(huì)加一,說明count 被function持有了而且可以被修改。而function2和function的count是獨(dú)立的,不是共享的。

通過 jadx 反編譯可以看到:

public final class ClosureKt {
    @NotNull
    public static final Function0<Integer> returnFun() {
        IntRef intRef = new IntRef();
        intRef.element = 0;
        return (Function0) new 1<>(intRef);
    }

    public static final void main() {
        Function0 function = returnFun();
        Function0 function2 = returnFun();
        System.out.println(((Number) function.invoke()).intValue());
        System.out.println(((Number) function.invoke()).intValue());
        System.out.println(((Number) function2.invoke()).intValue());
        System.out.println(((Number) function2.invoke()).intValue());
    }
}

被閉包引用的 int 局部變量,會(huì)被封裝成 IntRef 這個(gè)類。這個(gè) IntRef 里面保存著 int 變量,原函數(shù)和閉包都可以通過 intRef 來讀寫 int 變量。Kotlin 正是通過這種辦法使得局部變量可修改。除了 IntRef,還有 LongRef,F(xiàn)loatRef 等,如果是非基礎(chǔ)類型,就統(tǒng)一用 ObjectRef 即可。

3. 捕獲變量

閉包可以訪問函數(shù)體之外的變量,這個(gè)過程稱為捕獲變量。

// 全局變量
var value = 0

fun main(args: Array<String>?) {
    // 局部變量
    var localValue = 20

    val result = { a: Int ->
        value++
        localValue++
        val c = a + value + localValue
        println(c)
    }

    result(30)

    println("value = $value")
    println("localValue = $localValue")
}

System.out: 52
System.out: value = 1
System.out: localValue = 21

閉包是捕獲 value 和 localValue 變量的 Lambda 表達(dá)式。

Java 與 Koltin 中 Lambda 捕獲局部變量區(qū)別
在函數(shù)不是“一等公民”的 Java 這里,匿名類其實(shí)就是代替閉包而存在的。只不過 Java 嚴(yán)格要求所有函數(shù)都需要在類里面,所以巧妙的把“聲明一個(gè)函數(shù)”這樣的行為變成了“聲明一個(gè)接口”或“重寫一個(gè)方法”。匿名類也可以捕獲當(dāng)前環(huán)境的 final 局部變量。但和閉包不一樣的是,匿名類無法修改捕獲的局部變量(final 不可修改)。而匿名類能引用 final 的局部變量,是因?yàn)樵诰幾g階段,會(huì)把該局部變量作為匿名類的構(gòu)造參數(shù)傳入。因?yàn)槟涿愋薷牡淖兞坎皇钦嬲木植孔兞浚亲约旱臉?gòu)造參數(shù),外部局部變量并沒有被修改。所以 Java 編譯器不允許匿名類引用非 final 變量。jdk7在 Lambda 體中只能讀取局部變量,不能修改局部變量。而 kotlin 中沒有這個(gè)限制,可以讀取和修改局部變量。如下面代碼:

// 聲明了一個(gè)Java代碼接口
public interface Clickable {
    void onClick();
}

// Java中的Lambda表達(dá)式局部變量捕獲
public class Closure {

    private void closure(Clickable clickable) {
        clickable.onClick();
    }

    public void main(ArrayList<String> args) {
        int count = 0;
        closure(() -> {
            count += 1; // 編譯錯(cuò)誤,count需要使用final修飾
        });
        System.out.println(count);
    }
}

這樣的Java代碼是編譯不過的,必須設(shè)置為 count 為 final 才能通過編譯,但又不能對 count 進(jìn)行修改,如果非要修改 count 只能把 count 聲明為 Closure 的成員變量。

對比 Kotlin 代碼實(shí)現(xiàn):

class Closure {

    private fun closure(clickable: Clickable) {
        clickable.onClick()
    }

    fun main(args: Array<String>) {
        var count: Int = 0
        closure(Clickable { count += 1 }) // 編譯正常
        println(count)  // 2
    }
}

再來看一個(gè)閉包的例子:

fun returnFun(): () -> Int {
    var count = 0
    return { count++ }
}

fun main() {
    val function = returnFun()
    val function2 = returnFun()
    println(function()) // 0
    println(function()) // 1
    println(function()) // 2

    println(function2()) // 0
    println(function2()) // 1
    println(function2()) // 2
}

每調(diào)用一次function(),count都會(huì)加一,說明count 被function持有了而且可以被修改。而function2和function的count是獨(dú)立的,不是共享的。

五. 擴(kuò)展函數(shù)

擴(kuò)展函數(shù)數(shù)是指在一個(gè)類上增加一種新的行為,甚至我們沒有這個(gè)類代碼的訪問權(quán)限。在Java中,通常會(huì)實(shí)現(xiàn)很多帶有static方法的工具類,而Kotlin中擴(kuò)展函數(shù)的一個(gè)優(yōu)勢是我們不需要在調(diào)用方法的時(shí)候把整個(gè)對象當(dāng)作參數(shù)傳入,它表現(xiàn)得就像是屬于這個(gè)類的一樣,而且我們可以使用this關(guān)鍵字和調(diào)用所有public方法。

fun 被擴(kuò)展類名.擴(kuò)展函數(shù)名( 參數(shù) ){

//實(shí)現(xiàn)代碼

}

Java調(diào)用擴(kuò)展函數(shù):

 擴(kuò)展類名Kt.擴(kuò)展函數(shù)名(參數(shù));

六. 內(nèi)聯(lián)函數(shù)

1. inline

如果沒有內(nèi)聯(lián)修飾符標(biāo)記函數(shù),在使用lambda帶來的性能開銷。舉個(gè)接收函數(shù)類型的例子:

//callAction 接受一個(gè)函數(shù)類型(lambda)
private fun callAction(action: () -> Unit) {
    println("call Action before")
    action()
    println("call Action after")
}

fun main(args: Array<String>) {
    callAction {
        println("call action")
    }
}

反編譯為:

public final void main(@NotNull String[] args) {
      callAction((Function0)null.INSTANCE);
}

 private final void callAction(Function0 action) {
      String var2 = "call Action before";
      boolean var3 = false;
      System.out.println(var2);
      action.invoke();
      var2 = "call Action after";
      var3 = false;
      System.out.println(var2);
   }

由此可見當(dāng)調(diào)用callAction(action: () -> Unit) 時(shí),傳遞的lambda會(huì)被Function0所代替,而Function0是一個(gè)被定義為如下的接口:

public interface Function0<out R> : Function<R> {
    public operator fun invoke(): R
}

在調(diào)用callAction時(shí),編譯器會(huì)額外生成一個(gè)Function0的實(shí)例傳遞給callAction,內(nèi)部會(huì)調(diào)用 Function0 的 invoke() 方法。到目前為止,我們知道使用lambda會(huì)帶來額外的性能開銷。通過內(nèi)聯(lián)函數(shù)消除lambda帶來的運(yùn)行時(shí)開銷。
被inline標(biāo)記的函數(shù)就是內(nèi)聯(lián)函數(shù),其原理就是:在編譯時(shí)期,把調(diào)用這個(gè)函數(shù)的地方用這個(gè)函數(shù)的方法體進(jìn)行替換。
在函數(shù)被使用的時(shí)候編譯器并不會(huì)生成函數(shù)調(diào)用的代碼,而是使用函數(shù)實(shí)現(xiàn)的真實(shí)代碼替換每一次的函數(shù)調(diào)用。還是拿 callAction(action: () -> Unit) 方法舉例,當(dāng)給該函數(shù)添加inline修飾符后,編譯后的調(diào)用代碼如下

public final void main(@NotNull String[] {
      ...省略無關(guān)緊要的代碼
      System.out.println("call Action before");
      System.out.println("call action");
      System.out.println("call Action after");
}

總結(jié)下:

  • 被inline修飾的函數(shù)叫內(nèi)聯(lián)函數(shù)。
  • 內(nèi)聯(lián)函數(shù)會(huì)在被調(diào)用的位置內(nèi)聯(lián)。內(nèi)聯(lián)函數(shù)的代碼會(huì)被拷貝到使用它的位置,并把lambda替換到其中。

在kotlin中l(wèi)ambda 表達(dá)式會(huì)被正常地編譯成匿名類。這表示每調(diào)用一次lambda 表達(dá)式,一個(gè)額外的類就會(huì)被創(chuàng)建。并且如果lambda 捕捉了某個(gè)變量,那么每次調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)新的對象。這會(huì)帶來運(yùn)行時(shí)的額外開銷,導(dǎo)致使用lambda 比使用一個(gè)直接執(zhí)行相同代碼的函數(shù)效率更低。
如果使用 inline 修飾符標(biāo)記一個(gè)函數(shù),在函數(shù)被使用的時(shí)候編譯器并不會(huì)生成函數(shù)調(diào)用的代碼,而是使用函數(shù)實(shí)現(xiàn)的真實(shí)代碼替換每一次的函數(shù)調(diào)用。

2. noinline

雖然內(nèi)聯(lián)非常好用,但是會(huì)出現(xiàn)這么一個(gè)問題,就是內(nèi)聯(lián)函數(shù)的參數(shù)(ps:參數(shù)是函數(shù),比如上面的body函數(shù))如果在內(nèi)聯(lián)函數(shù)的方法體內(nèi)被其他非內(nèi)聯(lián)函數(shù)調(diào)用,就會(huì)報(bào)錯(cuò)。
舉個(gè)栗子:

inline fun <T> mehtod(lock: Lock, body: () -> T): T {
            lock.lock()
            try {
                otherMehtod(body)//會(huì)報(bào)錯(cuò)
                return body()
            } finally {
                lock.unlock()
            }
    }

    fun <T> otherMehtod(body: ()-> T){

    }

原因:因?yàn)閙ethod是內(nèi)聯(lián)函數(shù),所以它的形參也是inline的,所以body就是inline的,但是在編譯時(shí)期,body已經(jīng)不是一個(gè)函數(shù)對象,而是一個(gè)具體的值,然而otherMehtod卻要接收一個(gè)body的函數(shù)對象,所以就編譯不通過了
解決方法:當(dāng)然就是加noinline了,它的作用就已經(jīng)非常明顯了.就是讓內(nèi)聯(lián)函數(shù)的形參函數(shù)不是內(nèi)聯(lián)的,保留原有的函數(shù)特征.
具體操作:

fun main(args: Array<String>) {
    val lock = ReentrantLock()
    mehtod(lock,{"body方法體"})
}

inline fun <T> mehtod(lock: Lock, noinline body: () -> T): T {
    lock.lock()
    try {
        otherMehtod(body)
        return body()
    } finally {
        lock.unlock()
    }
}

fun <T> otherMehtod(body: ()-> T){

}

這樣編譯時(shí)期這個(gè)body函數(shù)就不會(huì)被內(nèi)聯(lián)了
反編譯看下

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      ReentrantLock lock = new ReentrantLock();
      //這里是生成了一個(gè)函數(shù)對象
      Function0 body$iv = (Function0)null.INSTANCE;
      ((Lock)lock).lock();

      try {
         otherMehtod(body$iv);
         Object var3 = body$iv.invoke();
      } finally {
         ((Lock)lock).unlock();
      }

   }

   public static final Object mehtod(@NotNull Lock lock, @NotNull Function0 body) {
      Intrinsics.checkParameterIsNotNull(lock, "lock");
      Intrinsics.checkParameterIsNotNull(body, "body");
      lock.lock();

      Object var3;
      try {
         otherMehtod(body);
         var3 = body.invoke();
      } finally {
         InlineMarker.finallyStart(1);
         lock.unlock();
         InlineMarker.finallyEnd(1);
      }

      return var3;
   }

   public static final void otherMehtod(@NotNull Function0 body) {
      Intrinsics.checkParameterIsNotNull(body, "body");
   }
3. crossinline

很少用到crossinline修飾符,什么是crossinline呢,crossinline 的作用是內(nèi)聯(lián)函數(shù)中讓被標(biāo)記為crossinline 的lambda表達(dá)式不允許非局部返回。
在kotlin中,return 只可以用在有名字的函數(shù),或者匿名函數(shù)中,使得該函數(shù)執(zhí)行完畢。
而針對lambda表達(dá)式,你不能直接使用return
你可以使用return+label的形式,將這個(gè)lambda結(jié)束。
但是
若你的lambda應(yīng)用在一個(gè)內(nèi)聯(lián)函數(shù)的時(shí)候,這時(shí)候你可以在lambda中使用return
可以這么理解,內(nèi)聯(lián)函數(shù)在編譯的時(shí)候,將相關(guān)的代碼貼入你調(diào)用的地方。
lambda表達(dá)式就是一段代碼而已,這時(shí)候你在lambda中的return,相當(dāng)于在你調(diào)用的方法內(nèi)return
crossinline就是為了讓其不能直接return。

函數(shù)式編程——閉包
理解Kotlin函數(shù)式編程
函數(shù)式編程-Kotlin
函數(shù)式編程——閉包
kotlin 閉包簡單例子
理解Kotlin函數(shù)式編程
kotlin的內(nèi)聯(lián)函數(shù)的使用
inline,包治百病的性能良藥
理解Kotlin函數(shù)式編程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容