从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()

对我来说 工作中用这个比较多 用处也比较广泛 比如初始化  等等 很是随意



猜你喜欢

转载自blog.csdn.net/youth_never_go_away/article/details/79957782