Kotlin——高級篇(一):Lambda表達式詳解

通過前面一系列對Kotlin講解,相信你們已經能對Kotlin有了一個基本的認識。若是你又Java語言方面的編程經驗,你可能已經不知足前面的基礎語法了。從這篇文章起,就爲你們講解Kotlin語言中的高級操做。
Lambda語法在Java中已經被普遍的運用,咱們在開發Android中幾乎上每個項目也會在項目中接入Lambda插件,由於Lambda確實能簡少不少的代碼量。無獨有偶,在Kotlin中也是Lambda語法的,在這篇文章中就詳細的爲你們講解Lambda語法的編寫與使用,同時會後面的Kotlin——高級篇(二):高階函數詳解與標準的高階函數使用打下基礎。html

目錄

1、Lambda介紹

在上面已經提到了在Java中已經被普遍的運用,可是也是在Java8的時候才支持這種Lambda表達式。在其餘的編程語言中(例如:Scala語言)。而這種表達式是語法糖中的一種。值得慶幸的是,Kotlin一經開源成熟就已經支持這種語法。git

Lambda表達式的本質實際上是匿名函數,由於在其底層實現中仍是經過匿名函數來實現的。可是咱們在用的時候沒必要關心起底層實現。不過Lambda的出現確實是減小了代碼量的編寫,同時也是代碼變得更加簡潔明瞭。
Lambda做爲函數式編程的基礎,其語法也是至關簡單的。這裏先經過一段簡單的代碼演示沒讓你們瞭解Lambda表達式的簡潔之處。github

例:編程

// 這裏舉例一個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() }
複製代碼

2、Lambda使用

關於Lambda的使用,我這裏從兩個方面講解,一是先介紹Lambda表達式的特色,二是從Lambda的語法使用講解。閉包

2.一、Lambda表達式的特色

古人云:欲取之,先與之。編程語言

要學習Lambda表達式語法,必先了解其特色。我在這裏先總結出Lambda表達式的一些特徵。在下面講解到Lambda語法與實踐時你們就明白了。即:ide

  • Lambda表達式老是被大括號括着
  • 其參數(若是存在)在 -> 以前聲明(參數類型能夠省略)
  • 函數體(若是存在)在 -> 後面。

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是一個匿名函數。

三、Lambda實踐

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

3.一、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.二、下劃線(_)

在使用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.四、帶接收者的函數字面值

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.一、攜帶狀態

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

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.二、引用外部變量,並改變外部變量的值

例:

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

相關文章
相關標籤/搜索