經過前面一系列對Kotlin
講解,相信大家已經能對Kotlin
有了一個基本的認識。如果你又Java
語言方面的編程經驗,你可能已經不滿足前面的基礎語法了。從這篇文章起,就為大家講解Kotlin
語言中的高級操作。
Lambda
語法在Java
中已經被廣泛的運用,我們在開發Android
中幾乎上每一個項目也會在項目中接入Lambda
插件,因為Lambda
確實能簡少很多的代碼量。無獨有偶,在Kotlin
中也是Lambda
語法的,在這篇文章中就詳細的為大家講解Lambda
語法的編寫與使用,同時會后面的Kotlin——高級篇(二):高階函數詳解與標准的高階函數使用打下基礎。
目錄
一、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
最后一個的實現可能大家難以理解,但請不要迷茫,你繼續看下去,在下面的實踐和高階函數中會為大家介紹。
經過上面的實例講解與語法的介紹,我們對其作出一個總結:
lambda
表達式總是被大括號括着。- 定義完整的
Lambda
表達式如上面實例中的語法2,它有其完整的參數類型標注,與表達式返回值。當我們把一些類型標注省略的情況下,就如上面實例中的語法2的另外一種類型。當它推斷出的返回值類型不為'Unit'時,它的返回值即為->
符號后面代碼的最后一個(或只有一個)表達式的類型。- 在上面例子中語法3的情況表示為:
高階函數
,當Lambda
表達式作為其一個參數時,只為其表達式提供了參數類型與返回類型,所以,我們在調用此高階函數的時候我們要為該Lambda
表達式寫出它的具體實現。
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
表達式的幾點區別:
- 匿名函數的參數傳值,總是在小括號內部傳遞。而
Lambda
表達式傳值,可以有省略小括號的簡寫寫法。- 在一個不帶
標簽
的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
Github:Jteictors
我的掘金:Jetictors