從java到Kotlin學習四:Lambd編程

最近工作比較忙 草稿箱放一個月了 終於有時間接着寫

lambd是在java8中被引入的 使用起來非常簡便

函數參數代碼塊

  原始方式: 使用匿名內部類實現事件處理器(比如click事件監聽)   

bt.setOnclickListener(object:OnclickListener(){
   public void click(){
        ...
   }
})

  lambd:

bt.setOnclickListener({...})

是不是簡潔了很多

集合操作

 要求: 獲取年齡最大的人

   原始方法:

for(person in personList){
   if(person.age>maxAge){
      ...
   }
}

   lambd

   personList.maxBy{ it.age}

   具體爲什麼會這樣用 後面我們會具體聊

  接下來 我們看下 lambd的具體語法

lambd表達式

 lambd可以將函數作爲表達式 然後當做值來使用 那麼我們看下lambd表達式的語法

val sum=(x:Int,y:Int)->{x+y}

上面這段代碼就相當於

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

調用

sum(1,2)

那麼我們通過表達式 看下上面獲取最大年齡人的maxBy的用法  

peopleList.maxBy{it.age}//最簡潔的用法

如果不使用任何簡潔用法我們需要這樣寫

peopleList.maxBy({p:Person->p.age})

也就是我們把實參傳給這個函數   lambd接收這個參數 並返回年齡

然後我們對這段代碼進行簡化:

kotlin語法規定

如果lambd表達式是函數調用的最後一個實參 可以放到括號外面

如果lambd表達式是唯一參數 那麼可以省略括號 

a.b({lambd表達式})->a.b(){lambda表達式}->a.b{lambda表達式}

lambda 可以省略參數類型(如果上下文可以自動推導出類型)

personList.maxBy{p:Person->p.age}
省略
personList.maxby{p->p.age}

當實參名稱可以使用默認參數名稱沒有顯示的指示(不推薦 可能會引起參數不明確)

personList.maxBy{it.age}
在作用域中訪問變量

我們可以在lambd中訪問非finial變量 同時能夠在lambd中進行修改變量

我們都知道 局部變量的生命週期被函數限制,但是被lambda捕捉的變量,可以被存儲並稍後執行

 原理: 如果lambda 捕捉的是finial變量的時候,它的值和lambda代碼一起存儲,對於非finial變量 會封裝在一個特殊的包裝器中 這樣你可以改變這個值  包裝器的引用和lambda一起存儲 

成員引用

kotlin和java8一樣可以將函數轉化成一個值  使用::來轉化(類:: 成員)

val getAge=person::age
相當於
val getAge={p:person->p.age}

注:成員名稱後面不要加括號 

那麼上面的就可以進行改寫

personList.maxby{Person::age}

除了引用成員 我們還可以引用頂層函數

fun a(){...}
run(::a)

在這種情況下我們省略了類名 直接以::開頭

場景 1: 我們需要把lambda 委託給多個參數的sendEmail方法 那麼 一般我們習慣這樣寫

val action={p:Person,text:String->sendEmial(p,text)}
使用方法引用
val action =::sendEmail

場景2:我們需要延期執行初始化 那麼我們把初始化委託給一個變量

val creatPerson=::Person
val p=createPerson("小明",18)

PS:擴展方法和成員的引用是一樣的 


集合的函數式API(函數式編程)

filter和map

  從字面上來看  filter -->過濾  map-->變化成一個集合

那麼我們具體看下用法

var list=listOf{1,2,3,4}
list.filter{it%2==0}

上面我們的最終結果是2,4   那麼我們就知道 是將不符合我們條件的數據過濾掉

var list=listOf{1,2,3,4}
list.map{it*it}

結果:1,4,9,16 那麼我們可以看到集合元素沒有變化 但是 每個元素都是按照我們的表達式 進行了變化

也就是 map是用來操作我們集合元素的  

那麼我們看下 我們打印下所有人員的名字

perpleList.map{it.name}

那麼我們用每個元素的名字生成了一個新的集合 

改寫

peopleList.map{Perple::name}

ok 又複習了一遍

場景1 :  打印下 年齡大於30的人的名字 

perpleList.filter{it.age>30}.map{People::name}

場景2: 獲取年齡最大的人的名字

peopleList.filter{it.age==perpleList.maxBy{People::age}.age}.map{People::name}

上面我們的代碼 maxBy方法執行了n次  那麼我們進行優化

val maxAge=perpleList.maxBy{People::age}。age
peopleList.filter{it.age==maxAge}.map{People::name}

lambda 用起來簡單 但是隱藏了很多底層操作用的時候需要注意

上面的我們都是針對於單列集合進行的操作 那麼下面我們看下雙列集合的操作

val numbers=mapOf(0  to "zero",1 to "one")
numbers.mapValues(it.value.toUpperCae())

我們創建了一個map集合 放置了兩組數據(0,"zero")(1,"one")  我們對集合進行了變幻--(每組中的value進行upper操作)

結果 :(0,ZERO) (1,ONE)

那麼我們其他的 filterKey  filterValue  mapKey 就不再介紹了

all any count find的使用 

  • all:全部滿足條件
  • any:至少有一個滿足 
  • count:滿足的元素個數 (和size的區別後面會說)
  • find :找到一個滿足的元素

那麼我們看下具體用法 

var peopleList=listOf(People("張三",18),People("李四",15))
val canBeInClub18={p:People->p.age>=18}//年齡大於等於18歲表達式
peopleList.all(canBeInClub18)//false  李四不滿足
peopleList.any(canBeInClub18)//true 張三滿足 
peopleList.count(canBeInClub18)// 1
peopleList.find(canBeInClub18)//("張三",18)如果沒有找到就返回null   同義方法 findOrNull   

那麼我們基本上 大概瞭解用法  我們再看下count  我們可以換一種寫法

peopleList.filter(canBeInClub18).size

我們使用filter進行過濾 然後獲取新集合的size  確實 結果和count 是一樣的 但是這樣filter會創建一個新的集合 而count只會跟蹤滿足條件的元素個數 從而 我們認爲在這種情景下 count 更高效

groupBy --列表轉換成分組

這個和SQL操作其實一樣 就是根據條件進行分組 我們通過代碼來分析

var peopleList=listOf(People("張三",18),People("李四",15))
peopleList.groupBy{p:People->p.age>18}

  結果:

{true=[People(name=張三, age=18)], false=[People(name=李四, age=15)]}

返回類型 Map<表達式返回類型,操作集合>  

我們這裏的返回值爲 Map<Boolean,List<People>>

場景1:按照年齡進行分組 

var peopleList=listOf(People("張三",18),People("李四",15))
peopleList.groupBy{p:People->p.age}
結果
{18=[People(name=張三, age=18)], 15=[People(name=李四, age=15)]}

不解釋

flatMap 和 flatten

flatMap:可以分爲兩個詞看  flat 和map  map我們之前就接觸過 對集合中每個元素進行變幻  flat 平鋪 在這裏我們理解成集合的合併

場景1: 合併字符串

val strings=listOf("abc","de")
string.flatMap{it.toList}

結果:

[a,b,c,d,e]

我們看下  :

首先 先對集合中 每個元素進行變幻  

“abc”->[a,b,c]  "de"->[d,e]

合併  [a,b,c,d,e]

flatten:當我們只需要平鋪一個集合的時候 不需要做任何變化 那麼可以使用  不介紹了 

惰性集合操作:序列

優點 : 序列的元素是惰性的  ,不需要創建中間集合  對於大型集合操作效率比較高 

什麼意思呢 我們看下 下面這個demo

peopleList.map{it.age}.filter(it>18)

上面集合的意思 我不在解釋了   我們前面說過 map 會創建一個新的集合  filter也會創建一個新的集合  那麼我們如果有幾百萬數據  兩個臨時集合的創建 需要耗費大量的性能  

我們用下序列

peopleList.asSequence().map{it.age}.filter(it>18).toList()

asSequence:將任意集合轉換成序列 

toList:將序列轉化成集合

這樣我們就不需要額外的集合來保存中間結果了   

最後說下爲什麼還要轉回List :如果只是迭代元素 我們完全不必要  但是如果涉及到api操作  ,比如用下標訪問元素等 

執行序列操作:中間和末端操作

peopleList.asSequence().map{it.age}.filter(it>18).toList()

還是以這個爲例子講解  

 map  filter  爲中間操作  返回的是一個序列  

 toList 是末端操作 返回的是一個結果

感覺並沒有什麼卵用  toList我們上邊說 如果不使用api可以不用轉  

那麼如果我們去掉 末端操作 

peopleList.asSequence().map{ Log.d("Log","age=${it.age}");it.age}.filter(
Log.d("Log","age=${it.age}");it>18)

這樣的話 我們只保留中間操作 最終結果我們是沒有任何結果輸出  什麼意思

中間操作是惰性的 延期的  只有我們執行了末端操作才能執行所有延期計算  也就是執行末端操作 中間操作才能被觸發 

PS:計算執行 

正常我們看到上面代碼 都認爲是從左到右 依次執行 也就是先執行完map  然後再執行filter  

然而 序列化並不是這樣 :所有操作 按順序執行在每一個元素上面 處理完第一個元素 再處理第二個  

我們看下這種計算執行的有點 

var peopleList=listOf(People("張三",18),People("李四",15)) 
peopleList.asSequence().map{it.age}.find(it>15)

那麼我們對第一個元素進行處理完成之後 就已經有結果了  那麼此時我們就可以跳過其他部分元素 

ok  大概就說到這 點到爲止  關於其他序列化的 可以看看官網

java函數式接口

首先我們這裏先介紹一個概念SAM接口 

SAM接口 也稱爲函數式接口  SAM 是單抽象方法的簡寫  也就是 只有一個抽象方法的接口 

interface Clickable {
    fun click()
}

這個就是單抽象方法  常見的比如  Runnable  CallBack等等 

kotlin 允許我們再調用函數式節後作爲參數的時候 使用lambda

lambd作爲參數傳遞給java

直接寫個demo  

fun io(runnable:Runnable){
thread(runnable).start()
}

fun requestData{
  io(){
//TODO 網絡請求 
  }
}

上面這個 是一個子線程的封裝方法  用到了lambd的傳遞  我們來簡單看下

Runnable  我們都知道是一個接口 所以我們一般使用java  

new Runnable(){
@override
public void  run(){
   ...
  }
}

這裏我們就能看出  lambd 會被變異成一個匿名內部類 

SAM構造方法 (顯示的把lambda 轉化成函數接口)

java 

public Runnable getRunnable(){
   Runnable runnable=new Runnable({
//TODO 
});
return runnable;
}

kotlin

fun createRunnable():Runnable{
    return Runnable { //TODO... }
}

那麼 我們之前已經說過 kotlin創建對象省略new  同時我們知道Runnable 是一個單方法的接口 那麼就符合我們SAM規則

下面我們看下用法

/**
 * 創建子線程執行function
 */
fun io(function: () -> Unit) {
    Thread { Runnable { function } }
}
fun setOnClick(){
    TextView(mContext).setOnClickListener { 
//        todo
    }
}
帶接受者的lambda(with &apply)


with:可以對同一個對象執行多次操作 不需要反覆帶着對象.  來調用 有人說並沒有什麼用處  但是 確實寫代碼快了很多

java 

Person person=new Person()
person.name="小張"
person.age=18
kotlin with
var person =Person()
with(person){
  this.name="小張"
  this.age=18
toString()
}


是不是省了兩個對象 怕不怕  那我們看下爲什麼可以這樣簡寫  我們都要明白一個道理  你省事了 必然有地方幫你做這些事

原理:with實際上是一個能夠接收兩個參數的函數  通過下面的源碼我們可以看到 只是最後的方法 被放到括號外面了 (以前我們說過 爲什麼可以這樣 這裏不說了)  

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

with方法是將我們的第一個參數 person  作爲參數傳遞給lambda  然後我們可以通過this 來調用person 對象 此時this可以省略 

此時我們明白 這個this 指向的是函數接收者 

apply 函數 

apply 和with 基本上 是一樣的 只是返回值不同的問題  with 是沒有返回值的  apply 返回值是傳入的對象本身  那麼我們看下 apply的用法

val person =Person()
person.apply{
 name="小張"
 age=18
}.toString()

對我來說 工作中用這個比較多 用處也比較廣泛 比如初始化  等等 很是隨意



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章