天天看點

Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()->Unit 和 ()->Unit 的差別

Kotlin——Lambda表達式詳解

文章目錄

  • 一、為什麼要使用Kotlin的lambda表達式?
  • 二、Kotlin的lambda表達式基本文法
    • 1、lambda表達式分類
    • 2、lambda基本文法
    • 3、lambda文法簡化轉換
    • 4、lambda表達式的傳回值
    • 5、lambda表達式類型
  • 帶有接收者的函數常量
  • Kotlin中,函數作為參數,T.()->Unit 和 ()->Unit 的差別

參考

Kotlin——進階篇(一):Lambda表達式詳解

Kotlin——進階篇(二):高階函數詳解與标準的高階函數使用

掌握Kotlin标準函數:run, with, let, also and apply

Kotlin系列之Lambda表達式完全解析

Kotlin中,函數作為參數,T.()->Unit 和 ()->Unit 的差別

簡述: 今天帶來的Kotlin淺談系列的第六彈, 一起來聊下Kotlin中的lambda表達式。lambda表達式應該都不陌生,在Java8中引入的一個很重要的特性,将開發者從原來繁瑣的文法中解放出來,可是很遺憾的是隻有Java8版本才能使用。而Kotlin則彌補了這一問題,Kotlin中的lambda表達式與Java混合程式設計可以支援Java8以下的版本。那我們帶着以下幾個問題一起來看下Kotlin中lambda表達式。

  1. 為什麼要使用Kotlin的lambda表達式(why)?
  2. 如何去使用Kotlin的lambda表達式(how)?
  3. Kotlin的lambda表達式一般用在哪(where)?
  4. Kotlin的lambda表達式的作用域變量和變量捕獲
  5. Kotlin的lambda表達式的成員引用

一、為什麼要使用Kotlin的lambda表達式?

針對以上為什麼使用Kotlin中的lambda表達式的問題,我覺得有三點主要的原因。

  1. Kotlin的lambda表達式以更加簡潔易懂的文法實作功能,使開發者從原有備援啰嗦的文法聲明解放出來。可以使用函數式程式設計中的過濾、映射、轉換等操作符處理集合資料,進而使你的代碼更加接近函數式程式設計的風格。
  2. Java8以下的版本不支援Lambda表達式,而Kotlin則相容與Java8以下版本有很好互操作性,非常适合Java8以下版本與Kotlin混合開發的模式。解決了Java8以下版本不能使用lambda表達式瓶頸。
  3. 在Java8版本中使用Lambda表達式是有些限制的,它不是真正意義上支援閉包,而Kotlin中lambda才是真正意義的支援閉包實作。(關于這個問題為什麼下面會有闡述)

二、Kotlin的lambda表達式基本文法

1、lambda表達式分類

在Kotlin實際上可以把Lambda表達式分為兩個大類,

  • 一個是普通的lambda表達式
  • 另一個則是帶接收者的lambda表達式(功能很強大,之後會有專門分析的部落格)。

    這兩種lambda在使用和使用場景也是有很大的不同. 先看下以下兩種lambda表達式的類型聲明:

    Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()->Unit 和 ()->Unit 的差別

針對帶接收者的Lambda表達式在Kotlin中标準庫函數中也是非常常見的比如with,apply标準函數的聲明。

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

           

看到以上的lambda表達式的分類,你是不是想到之前的擴充函數了,有沒有想起之前這張圖?

Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

是不是和我們之前部落格說普通函數和擴充函數類似。普通的Lambda表達式類似對應普通函數的聲明,而帶接收者的lambda表達式則類似對應擴充函數。擴充函數就是這種聲明接收者類型,然後使用接收者對象調用直接類似成員函數調用,實際内部是通過這個接收者對象執行個體直接通路它的方法和屬性。

2、lambda基本文法

lambda的标準形式基本聲明滿足三個條件:

  1. 含有實際參數
  2. 含有函數體(盡管函數體為空,也得聲明出來)
  3. 以上内部必須被包含在花括号内部
Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

以上是lambda表達式最标準的形式,可能這種标準形式在以後的開發中可能見到比較少,更多是更加的簡化形式,下面就是會介紹Lambda表達式簡化規則

3、lambda文法簡化轉換

以後開發中我們更多的是使用簡化版本的lambda表達式,因為看到标準的lambda表達式形式還是有些啰嗦,比如實參類型就可以省略,因為Kotlin這門語言支援根據上下文環境智能推導出類型,是以可以省略,摒棄啰嗦的文法,下面是lambda簡化規則。

Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

注意:文法簡化是把雙刃劍,簡化固然不錯,使用簡單友善,但是不能濫用,也需要考慮到代碼的可讀性.上圖中Lambda化簡成的最簡單形式用it這種,一般在多個Lambda嵌套的時候不建議使用,嚴重造成代碼可讀性,到最後估計連開發者都不知道it指代什麼了。比如以下代碼:

4、lambda表達式的傳回值

lambda表達式傳回值總是傳回函數體内部最後一行表達式的值

package com.mikyou.kotlin.lambda

fun main(args: Array<String>) {

    val isOddNumber = { number: Int ->
        println("number is $number")
        number % 2 == 1
    }

    println(isOddNumber.invoke(100))
}
 
           
Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

将函數體内的兩個表達式互換位置後

package com.mikyou.kotlin.lambda

fun main(args: Array<String>) {

    val isOddNumber = { number: Int ->
        number % 2 == 1
        println("number is $number")
    }

    println(isOddNumber.invoke(100))
}
 
           
Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

通過上面例子可以看出lambda表達式是傳回函數體内最後一行表達式的值,由于println函數沒有傳回值,是以預設列印出來的是Unit類型,那它内部原理是什麼呢?實際上是通過最後一行表達式傳回值類型作為了invoke函數的傳回值的類型

5、lambda表達式類型

Kotlin中提供了簡潔的文法去定義函數的類型.

() -> Unit//表示無參數無傳回值的Lambda表達式類型

(T) -> Unit//表示接收一個T類型參數,無傳回值的Lambda表達式類型

(T) -> R//表示接收一個T類型參數,傳回一個R類型值的Lambda表達式類型

(T, P) -> R//表示接收一個T類型和P類型的參數,傳回一個R類型值的Lambda表達式類型

(T, (P,Q) -> S) -> R//表示接收一個T類型參數和一個接收P、Q類型兩個參數并傳回一個S類型的值的Lambda表達式類型參數,傳回一個R類型值的Lambda表達式類型
 
           

上面幾種類型前面幾種應該好了解,估計有點難度是最後一種,最後一種實際上已經屬于高階函數的範疇。不過這裡說下個人看這種類型的一個方法有點像剝洋蔥一層一層往内層拆分,就是由外往裡看,然後做拆分,對于本身是一個Lambda表達式類型的,先暫時看做一個整體,這樣就可以确定最外層的Lambda類型,然後再用類似方法往内部拆分。

Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

帶有接收者的函數常量

Kotlin可以調用帶有接收者的函數常量。在函數常量的函數體中,可以不使用任何額外限定符,調用接收者的方法,這與擴充函數相似:允許通路函數體内部接收者的成員。其中一個例子Type-safe Groovy-style builders

這種函數常量類型實際是帶有接收者的函數類型

sum: Int.(other: Int)->Int
           

如果函數常量是接收者的一個方法,則可以直接調用。

1.sum(2)
           

匿名函數文法可以直接指定函數常量接收者類型。可以聲明帶有接收者的函數類型變量,并在之後調用。

val sum = fun Int.(other:Int):Int=this+other
           

當可以從上下文中推斷接收者類型時,Lambda表達式可以用作帶有接收者的函數常量。

class HTML{
    fun body(){...}
    fun html(init: HTML.() -> Unit): Unit{
        val html = HTML()
        html.init()
        return html;
    }
    
}
html{
    body()
}
           

Kotlin中,函數作為參數,T.()->Unit 和 ()->Unit 的差別

在做kotlin開發中,經常看到一些系統函數裡,用函數作為參數,但是又和我們自己寫的不太一樣

大概是這樣子的:

public inline fun <T> T.apply(block: T.() -> Unit): T
{
    block()
    return this
}
           

一開始的時候,我很疑惑,我們平時定義的是這樣子的啊:

fun <T : View> T.hahaha(f: () -> Unit)
{

}
           

這裡就很疑惑了,為什麼?T不是一個類嗎?怎麼可以直接 T.() 這是什麼意思??

我們這裡來看一下文檔是怎麼說的,

Kotlin——Lambda表達式詳解Kotlin——Lambda表達式詳解一、為什麼要使用Kotlin的lambda表達式?二、Kotlin的lambda表達式基本文法帶有接收者的函數常量Kotlin中,函數作為參數,T.()-&gt;Unit 和 ()-&gt;Unit 的差別

原來這裡作用就是可以this代表的對象的不同。

這兩個函數唯一的差別就是

T.()-Unit與()->Unit的差別,我們調用時,在代碼塊裡面寫this的時候,根據代碼提示,我們可以看到,這個this代表的含義不一樣,T.()->Unit裡的this代表的是自身執行個體,而()->Unit裡,this代表的是外部類的執行個體

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}
           

HTML

類執行個體是

init

函數的調用者。也可以攜帶參數

fun html(init: HTML.(x: Int) -> Unit): HTML {
    val html = HTML()
    html.init(1)
    return html
}