最近工作比較忙 草稿箱放一個月了 終於有時間接着寫
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
}
}
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()
對我來說 工作中用這個比較多 用處也比較廣泛 比如初始化 等等 很是隨意