最近工作比较忙 草稿箱放一个月了 终于有时间接着写
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()
对我来说 工作中用这个比较多 用处也比较广泛 比如初始化 等等 很是随意