Kotlin 学习笔记05

195 阅读6分钟

Lambda编程

本质就是给其他函数传递一小段代码

  • 当做事件处理器。
  • 把某个操作应用到集合的所有元素上。

在Android中 常见的点击事件可以这样写

 button.setOnclickListener{/*do something*/}

查找一个Person集合中,年龄中最大一个人

//找到最大年龄的人
fun findTheOldest(personList: Collection<Person>): Person? {
    var tempOldest = 0
    var oldestPerson: Person? = null
    for (person in personList) {
        if (tempOldest < person.age) {
            tempOldest = person.age
            oldestPerson = person
        }
    }
    return oldestPerson
}

使用库函数可以非常简洁

val people = listOf(Person("Bob",12),Person("Lili",19))
printLn(people.maxBy{it.age})//这样就可以得出最大年龄的人 其中it表示这个集合的一个元素
//如果lambda刚好是函数或者属性的委托,可以用成员引用代替
people.maxBy(Person::age)

lambda表达式语法

kotlinlambda在大括号内

  • 可以吧lambda存储在一个变量,方便调用
val sum = { x: Int, y: Int -> x + y } //这里的箭头是把参数和函数体分开

lambda可以直接调用

{ x: Int, y: Int -> println(x + y )}(4,6)//直接打印结果10

//上面Person例子,用完整的lambda应该是这样的
val people = listOf(Person("Bob",12),Person("Lili",19))
people.maxBy ({ p: Person -> p.age }) //注意这里的括号
//在kotlin中可以写成
people.maxBy{it.age}

把一个people 名字打印出来,可以这样

val people = listOf(Person("Bob",12),Person("Lili",19))
println(people.joinToString { it.name }) //可以得到“Bob,Lili” 但我需要的是“Bob Lili”
//可以这样
println(people.joinToString(" ", transform = { it.name })) //这里的transform?
  • 最后一个表达式就是lambda的结果
val sum = { x: Int, y: Int ->
    print("Counting the sum $x and $y is ...")
    x + y
}

在上下文中捕获变量

接收一个列表和一个前缀,给每个列表前面加上这个前缀,并打印出来

fun printMessageWithPrefix(people: Collection<String>, prefix: String) {
    people.forEach { println("$prefix $it") }
}

kotlin中的lambda允许内部类访问非final变量,甚至修改他们 局部变量只会在lambda执行的时候发生

  • 成员引用 ::符号
    可以解决一个问题:如果你的代码已经被定义成一个函数,而你又需要把这个函数作为一个值传递的情况。

people.maxBy(Person::age) 
//如果引用的是顶层函数,还可以把类名省略
people.maxBy(::age) 

如果lambda要委托给一个接受多个参数的函数,提供成员引用会很方便

val action = { person: Person, message: String ->
    sendEmail(person, message)
}
val nextAction = ::sendEmail
  • 可以用构造方法应用存储或延时执行创建类的动作
val createPerson = ::Person
createPerson("Bob",10)
  • 引用拓展函数
fun Person.dance() = {}
//引用
val dance = Person::dance

集合的函数式API

  • 基础:filter(过滤)和map(转换)
val num = listOf(1, 2, 3, 4, 5, 6, 88, 7, 99, 21)
println(num.filter { it % 2 == 0 })

val num = listOf(1, 2, 3, 4, 5, 6, 88, 7, 99, 21)
println(num.filter { it % 2 == 0 })
val person = listOf(Person("Bob", 12), Person("Lili", 20), Person("王小丫", 8))
println(person.filter { it.age > 10 }.joinToString(" ", transform = Person::name))
println(num.map { it * it })
  • all any countfind:对集合应用判断式
    all any通过函数表达式,count检查有多少元素满足判断式,find函数返回第一个复合条件的元素

*判断是否所有元素都满足判断式:all

val person = listOf(Person("Bob", 12), Person("Mark", 20), Person("王小丫", 8))
//all
println(person.all { it.age > 12 })//false
  • 检查集合中是否存在至少有一项匹配:any
println(person.any { it.age == 20 }) //true
  • 查找符合条件的数量:count
person.count{ it.age > 10} //2

countsize的区别在于:在使用函数集合API统计个数应该使用count,使用size会创建一个中间集合

  • 把列表转换成分组的map:groupBy:比如你想把年龄相同的人放到一个集合
println(person.groupBy { it.age })//得出来的结果是一个map(age, list<Person>)

eg:下面字符串集合按照首字母分组

val list = listOf("a", "ab", "bb", "bc", "zz", "gg", "jz", "zb")
//实现
println(list.groupBy(String::first)) //使用成员引用
println(list.groupBy { it.first() })//使用lambda表达式
  • 处理嵌套集合中的元素:flatMapfaltten(?没找到这个函数😓)
    eg:统计出图书馆中所有书作者(每一本书的作者可以有很多个),放到一个集合中
data class Book(val tilte: String,val authors: String)
//1.根据给定参数,对每个元素做变换(映射)
//2.把多个列表合并成一个列表
bookLibs.flatMap { it.authors }.toSet()
//如果不需要转换,则使用flatten()API

mapflatMap的区别
map:给集合每一个元素按一个方法处理得出一个大小和处理前集合大小一样集合
flatMap:对元素自身进行变化操作,得出一个集合


惰性集合操作:序列 asSequence

链式集合调用,例如flatMap()、toSet(),没执行一步中间结果都会变存储到一个临时列表 序列 可以避免创建中间 临时变量 当我们需要处理元素很多,可以使用asSequence序列,序列之后不能对标进行操作 提供一个iterator获取序列中值

bookLibs.asSequence()
        .map { it.tilte }
        .filter { it.startsWith("B") }
        .toList()
  • 序列的使用方式 中间和末端操作
    序列是逐个处理元素、集合是所有数据一个一个操作符处理的
val num = listOf(1, 2, 3, 4, 5)
    num.asSequence() 
        .map { print("$it");it * it } //不会打印任何东西
        .filter { print("filter$it");it % 2 == 0 } //不会打印任何东西
        

原因:map 和filter的操作被延迟了,只有在获取结果才会被应用(就是执行toList())
序列在操作符的顺序需要注意 比如:

    person.asSequence()
        .map(Person::name)//先执行映射再执行filter
        .filter { it.length < 4 }
        .toList()

    person.asSequence()
        .filter { it.name.length < 4 } //限制性filter再执行映射
        .map { it.name }
        .toList()

从图中可以看出来,第二种方案可以减少运行总次数


使用Java函数式接口

Android中使用lambda 设置按钮点击

button.setOnclickListener{/**点击之后的动作**/}

在Java8之前

button.setOnclickListener(new OnclickListener(){
        @Override
        public void onClick(View v){
            ...
        }
    })

在kotlin中

button.setOnClickListener{view -> ...}

lambda作为参数传递给Java方法可以把lambda传递给任何期望函数式接口的方法
在Java中

new Handler().postDelayed(new Runable(){},200)
Handler().postDelayed({ print("running !!!")},200)
  • SAM构造方法:可以显式的把lambda转换成函数式接口 让你执行从lambda到函数式接口实例的显示转换。可以在编译器不会自动转换的上下文中使用
    比如:有一个方法返回的是一个函数式接口实例,不能直接返回一个lambda,要使用SAM构造方法把它包起来。
fun createAllDoneRunnable():Runnable{
    return Runnable { print("All Runnable Done!") }
}

使用SAM构造方法重用listener实例

val onClickListener = View.OnClickListener {
            val text = when (it.id) {
                R.id.Button1 -> "Button1"
                R.id.Button2 -> "Button2"
                else -> "Unknown button"
            }
            toast(text)
        }
//调用
button1.setOnClickListener(onClickListener)
button2.setOnClickListener(onClickListener)

lambda 是一个代码块,不是一个对象,so 如果需要添加/移除监听器不能使用lambda这样做

带接受者的lambda :withapply

with可以对同一个对象执行多次而不需要反复把对象名称写出来
构建字母表

fun alphabet(): String {
    val result = StringBuilder()
    for (a in 'A'..'Z') {
        result.append(a)
    }
    return result.toString()
}

使用with

fun alphabet(): String {
    val result = StringBuilder()
    return with(result) {
        for (letter in 'A'..'Z') {
            this.append(letter) //这里的this执行的是函数接受者
        }
        toString()//this 可以省略
    }
}

上面with其实是接收2个参数的函数,第一个是StringBuilder,第二个是lambda。这里利用了kotlin约定,lambda可以放到括号外。完整应该是

with(result, {...})

拓展函数类似于一个带接收者的lambda,this执行的是函数拓展那个类型实例
上面例子进一步优化

fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter)//省略this
    }
    toString()//省略this
}

apply函数

apply几乎和with一样,唯一区别,apply会返回传递给他的对象。下面使用apply重构上面代码

fun alphabet() = StringBuilder().apply {
    for (letter in 'A'..'Z') append(letter)
}.toString()
  • apply 常用于创建一个对象实例并需要争取的方式初始化一些属性的时候。比如下面的例子:
val textView = TextView(this).apply {
            text = "Hello world"
            textSize = 12F
            setPadding(12, 0, 0, 0)
}

End