Kotlin——高階篇(一):Lambda表示式詳解

Jetictors發表於2018-03-27

Kotlin——高階篇(一):Lambda表示式詳解

經過前面一系列對Kotlin講解,相信大家已經能對Kotlin有了一個基本的認識。如果你又Java語言方面的程式設計經驗,你可能已經不滿足前面的基礎語法了。從這篇文章起,就為大家講解Kotlin語言中的高階操作。
Lambda語法在Java中已經被廣泛的運用,我們在開發Android中幾乎上每一個專案也會在專案中接入Lambda外掛,因為Lambda確實能簡少很多的程式碼量。無獨有偶,在Kotlin中也是Lambda語法的,在這篇文章中就詳細的為大家講解Lambda語法的編寫與使用,同時會後面的Kotlin——高階篇(二):高階函式詳解與標準的高階函式使用打下基礎。

目錄

Kotlin——高階篇(一):Lambda表示式詳解

一、Lambda介紹

在上面已經提到了在Java中已經被廣泛的運用,但是也是在Java8的時候才支援這種Lambda表示式。在其他的程式語言中(例如:Scala語言)。而這種表示式是語法糖中的一種。值得慶幸的是,Kotlin一經開源成熟就已經支援這種語法。

Lambda表示式的本質其實是匿名函式,因為在其底層實現中還是通過匿名函式來實現的。但是我們在用的時候不必關心起底層實現。不過Lambda的出現確實是減少了程式碼量的編寫,同時也是程式碼變得更加簡潔明瞭。
Lambda作為函數語言程式設計的基礎,其語法也是相當簡單的。這裡先通過一段簡單的程式碼演示沒讓大家瞭解Lambda表示式的簡潔之處。

例:

// 這裡舉例一個Android中最常見的按鈕點選事件的例子
mBtn.setOnClickListener(object : View.OnClickListener{
        override fun onClick(v: View?) {
            Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show()
        }
    })
複製程式碼

等價於

// 呼叫
mBtn.setOnClickListener { Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() }
複製程式碼

二、Lambda使用

關於Lambda的使用,我這裡從兩個方面講解,一是先介紹Lambda表示式的特點,二是從Lambda的語法使用講解。

2.1、Lambda表示式的特點

古人云:欲取之,先與之。

要學習Lambda表示式語法,必先了解其特點。我在這裡先總結出Lambda表示式的一些特徵。在下面講解到Lambda語法與實踐時大家就明白了。即:

  • Lambda表示式總是被大括號括著
  • 其引數(如果存在)在 -> 之前宣告(引數型別可以省略)
  • 函式體(如果存在)在 -> 後面。

2.2、Lambda語法

為了讓大家徹底的弄明白Lambda語法,我這裡用三種用法來講解。並且舉例為大家說明

語法如下:

1. 無引數的情況 :
val/var 變數名 = { 操作的程式碼 }

2. 有引數的情況
val/var 變數名 : (引數的型別,引數型別,...) -> 返回值型別 = {引數1,引數2,... -> 操作引數的程式碼 }

可等價於
// 此種寫法:即表示式的返回值型別會根據操作的程式碼自推匯出來。
val/var 變數名 = { 引數1 : 型別,引數2 : 型別, ... -> 操作引數的程式碼 }

3. lambda表示式作為函式中的引數的時候,這裡舉一個例子:
fun test(a : Int, 引數名 : (引數1 : 型別,引數2 : 型別, ... ) -> 表示式返回型別){
    ...
}
複製程式碼

例項講解:

  • 無引數的情況

    // 原始碼
    fun test(){ println("無引數") }
      
    // lambda程式碼
    val test = { println("無引數") }
    
    // 呼叫
    test()  => 結果為:無引數
    複製程式碼
  • 有引數的情況,這裡舉例一個兩個引數的例子,目的只為大家演示

    // 原始碼
    fun test(a : Int , b : Int) : Int{
        return a + b
    }
    
    // lambda
    val test : (Int , Int) -> Int = {a , b -> a + b}
    // 或者
    val test = {a : Int , b : Int -> a + b}
    
    // 呼叫
    test(3,5) => 結果為:8
    複製程式碼
  • lambda表示式作為函式中的引數的時候

    // 原始碼
    fun test(a : Int , b : Int) : Int{
        return a + b
    }
    
    fun sum(num1 : Int , num2 : Int) : Int{
        return num1 + num2
    }
    
    // 呼叫
    test(10,sum(3,5)) // 結果為:18
    
    // lambda
    fun test(a : Int , b : (num1 : Int , num2 : Int) -> Int) : Int{
        return a + b.invoke(3,5)
    }
    
    // 呼叫
    test(10,{ num1: Int, num2: Int ->  num1 + num2 })  // 結果為:18
    複製程式碼

最後一個的實現可能大家難以理解,但請不要迷茫,你繼續看下去,在下面的實踐和高階函式中會為大家介紹。

經過上面的例項講解與語法的介紹,我們對其作出一個總結:

  1. lambda表示式總是被大括號括著。
  2. 定義完整的Lambda表示式如上面例項中的語法2,它有其完整的引數型別標註,與表示式返回值。當我們把一些型別標註省略的情況下,就如上面例項中的語法2的另外一種型別。當它推斷出的返回值型別不為'Unit'時,它的返回值即為->符號後面程式碼的最後一個(或只有一個)表示式的型別。
  3. 在上面例子中語法3的情況表示為:高階函式,當Lambda表示式作為其一個引數時,只為其表示式提供了引數型別與返回型別,所以,我們在呼叫此高階函式的時候我們要為該Lambda表示式寫出它的具體實現。
  1. invoke()函式:表示為通過函式變數呼叫自身,因為上面例子中的變數b是一個匿名函式。

3、Lambda實踐

學會了上面講解的語法只是,相信您已能大致的編寫且使用lambda表示式了,不過只會上面簡單的語法還不足以運用於實際專案中複雜的情況。下面從幾個知識點講解Lambda實踐的要點。

3.1、it

  • it並不是Kotlin中的一個關鍵字(保留字)。
  • it是在當一個高階函式中Lambda表示式的引數只有一個的時候可以使用it來使用此引數。it可表示為單個引數的隱式名稱,是Kotlin語言約定的。

例1:

val it : Int = 0  // 即it不是`Kotlin`中的關鍵字。可用於變數名稱
複製程式碼

例2:單個引數的隱式名稱

// 這裡舉例一個語言自帶的一個高階函式filter,此函式的作用是過濾掉不滿足條件的值。
val arr = arrayOf(1,3,5,7,9)
// 過濾掉陣列中元素小於2的元素,取其第一個列印。這裡的it就表示每一個元素。
println(arr.filter { it < 5 }.component1())   
複製程式碼

例2這個列子只是給大家it的使用,filter高階函式,在後面的Kotlin——高階篇(四):集合(Array、List、Set、Map)基礎章節中會為大家詳細講解,這裡不多做介紹。下面為我們自己寫一個高階函式去講解it。關於高階函式的定義與使用請參見Kotlin——高階篇(二):高階函式詳解與標準的高階函式使用這篇文章。

例3:

 fun test(num1 : Int, bool : (Int) -> Boolean) : Int{
   return if (bool(num1)){ num1 } else 0
}

println(test(10,{it > 5}))
println(test(4,{it > 5}))
複製程式碼

輸出結果為:

10
0
複製程式碼

程式碼講解:上面的程式碼意思是,在高階函式test中,其返回值為Int型別,Lambda表示式以num1位條件。其中如果Lambda表示式的值為false的時候返回0,反之返回num1。故而當條件為num1 > 5這個條件時,當呼叫test函式,num1 = 10返回值就是10,num1 = 4返回值就是0。

3.2、下劃線(_)

在使用Lambda表示式的時候,可以用下劃線(_)表示未使用的引數,表示不處理這個引數。

同時在遍歷一個Map集合的時候,這當非常有用。

舉例:

val map = mapOf("key1" to "value1","key2" to "value2","key3" to "value3")

map.forEach{
     key , value -> println("$key \t $value")
}

// 不需要key的時候
map.forEach{
    _ , value -> println("$value")
}
複製程式碼

輸出結果:

key1 	 value1
key2 	 value2
key3 	 value3
value1
value2
value3
複製程式碼

3.3 匿名函式

  • 匿名函式的特點是可以明確指定其返回值型別。
  • 它和常規函式的定義幾乎相似。他們的區別在於,匿名函式沒有函式名。

例:

            fun test(x : Int , y : Int) : Int{                  fun(x : Int , y : Int) : Int{
常規函式:      return x + y                        匿名函式:      return x + y
            }                                                   }
複製程式碼

在前面的Kotlin——初級篇(七):函式基礎總結我們講解過單表示式函式。故而,可以簡寫成下面的方式。

常規函式 : fun test(x : Int , y : Int) : Int = x + y
匿名函式 : fun(x : Int , y : Int) : Int = x + y
複製程式碼

從上面的兩個例子可以看出,匿名函式與常規函式的區別在於一個有函式名,一個沒有。

例項演練:

val test1 = fun(x : Int , y : Int) = x + y  // 當返回值可以自動推斷出來的時候,可以省略,和函式一樣
val test2 = fun(x : Int , y : Int) : Int = x + y
val test3 = fun(x : Int , y : Int) : Int{
    return x + y
}

println(test1(3,5))
println(test2(4,6))
println(test3(5,7))
複製程式碼

輸出結果為:

8
10
12
複製程式碼

從上面的程式碼我們可以總結出匿名函式Lambda表示式的幾點區別:

  1. 匿名函式的引數傳值,總是在小括號內部傳遞。而Lambda表示式傳值,可以有省略小括號的簡寫寫法。
  2. 在一個不帶標籤return語句中,匿名函式時返回值是返回自身函式的值,而Lambda表示式的返回值是將包含它的函式中返回。

3.4、帶接收者的函式字面值

kotlin中,提供了指定的接受者物件呼叫Lambda表示式的功能。在函式字面值的函式體中,可以呼叫該接收者物件上的方法而無需任何額外的限定符。它類似於擴充套件函式,它允你在函式體內訪問接收者物件的成員。

  • 匿名函式作為接收者型別

匿名函式語法允許你直接指定函式字面值的接收者型別,如果你需要使用帶接收者的函式型別宣告一個變數,並在之後使用它,這將非常有用。

例:

val iop = fun Int.( other : Int) : Int = this + other
println(2.iop(3))
複製程式碼

輸出結果為:

5
複製程式碼
  • Lambda表示式作為接收者型別

要用Lambda表示式作為接收者型別的前提是接收著型別可以從上下文中推斷出來

例:這裡用官方的一個例子做說明

class HTML {
    fun body() { …… }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // 建立接收者物件
    html.init()        // 將該接收者物件傳給該 lambda
    return html
}


html {       // 帶接收者的 lambda 由此開始
    body()   // 呼叫該接收者物件的一個方法
}
複製程式碼

3.5 閉包

  • 所謂閉包,即是函式中包含函式,這裡的函式我們可以包含(Lambda表示式,匿名函式,區域性函式,物件表示式)。我們熟知,函數語言程式設計是現在和未來良好的一種程式設計趨勢。故而Kotlin也有這一個特性。
  • 我們熟知,Java是不支援閉包的,Java是一種物件導向的程式語言,在Java中,物件是他的一等公民。函式變數是二等公民。
  • Kotlin中支援閉包,函式變數是它的一等公民,而物件則是它的二等公民了。

例項:看一段Java程式碼

public class TestJava{

    private void test(){
        private void test(){        // 錯誤,因為Java中不支援函式包含函式

        }
    }

    private void test1(){}          // 正確,Java中的函式只能包含在物件中+
}
複製程式碼

例項:看一段Kotlin程式碼

fun test1(){
    fun test2(){   // 正確,因為Kotlin中可以函式巢狀函式
        
    }
}
複製程式碼

下面我們講解Kotlin中幾種閉包的表現形式。

3.5.1、攜帶狀態

例:讓函式返回一個函式,並攜帶狀態值

fun test(b : Int): () -> Int{
    var a = 3
    return fun() : Int{
        a++
        return a + b
    }
}

val t = test(3)
println(t())
println(t())
println(t())
複製程式碼

輸出結果:

7
8
9
複製程式碼

3.5.2、引用外部變數,並改變外部變數的值

例:

var sum : Int = 0
val arr = arrayOf(1,3,5,7,9)
arr.filter { it < 7  }.forEach { sum += it }

println(sum)
複製程式碼

輸出結果:

9
複製程式碼

3.6 在Android開發中為RecyclerView的介面卡編寫一個Item點選事件

class TestAdapter(val context : Context , val data: MutableList<String>)
    : RecyclerView.Adapter<TestAdapter.TestViewHolder>(){

    private var mListener : ((Int , String) -> Unit)? = null

    override fun onBindViewHolder(holder: TestViewHolder?, position: Int) {
        ...
        holder?.itemView?.setOnClickListener {
            mListener?.invoke(position, data[position])
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): TestViewHolder {
        return TestViewHolder(View.inflate(context,layoutId,parent))
    }

    override fun getItemCount(): Int {
        return data.size
    }

    fun setOnItemClickListener(mListener : (position : Int, item : String) -> Unit){
        this.mListener = mListener
    }

    inner class TestViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView)
}

// 呼叫
TestAdapter(this,dataList).setOnItemClickListener { position, item ->
        Toast.makeText(this,"$position \t $item",Toast.LENGTH_SHORT).show()
    }
複製程式碼

總結

Lambda表示式是為我們減少了大量的程式碼,但是Lambda表示式是為後面的高階函式章節打下基礎,雖然在這篇文章中也提到了高階函式,但是都是最基礎的,在下一節中會為大家介紹自定義高階函式與Kotlin自身中常用的高階函式講解。

在這一章節中,講述了Lambda的語法、使用。以及Lambda表示式的一些特性與實踐操作。當然還包含了匿名函式這一知識點。其中最重要的當屬Lambda的實踐操作。如果你看完這篇文章還不甚理解,請在仔細的閱讀一遍並實際程式碼演練,因為在後面的高階函式章節還會遇到。

在這最後希望您能給個關注,因為您的關注,是我繼續寫文章最好的動力。

我的個人部落格Jetictors
GithubJteictors

歡迎各位大佬進群共同研究、探索

QQ群號:497071402

Kotlin——高階篇(一):Lambda表示式詳解

相關文章