接《Android开发者快速上手Kotlin(二) 之 面向对象编程》文章继续。
6 高阶函数 Kotlin中的高阶函数其实就是跟高等数学中的高阶函数一个概念,就是函数套函数,即f(g(x))。什么意思呢?其实很好理解,就是将函数本身看作一种类型,作为别的函数的参数传入或者作为返回值返回。我们在前面其实就已经接触过高阶函数,因为:arg.forEach(::println)中,forEach本身就是一个高阶函数,它接收的参数是:action: (T) -> Unit。来通过自定义是如何实现:
// 该函数返回一个函数
fun a(): (y: Int, z: Int) -> Int {
return { a: Int, b: Int -> a + b }
}// 该函数需要传入第二个参数是一个函数
fun b(int: Int, function: (string:String) -> Unit) {
function(int.toString())
}
// 调用
var aFun = a()// 返回一个函数
var result = aFun(2, 3)// 执行了返回的函数
b(result, ::println)// 输入println函数作为参数
说明 :
- a函数不接收参数,但返回了一个Lambda表达式: (y: Int, z: Int) -> Int。
- b函数接收一个Int和一个Lambda表达式:(string:String) -> Unit。
- 函数的引用使用两个冒号::,如果是类对象方法,就是obj::fun()。
inline fun a(noinline function1: () -> Unit, function2: () -> Unit) {
function1()
function2()
}
6.2 常用高阶函数 6.2.1 let、run、with、also和apply
let、run、with、also和apply这5个高阶函数作用起基本差不多,只是在使用上有一点点区别。它们都是作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,这些函数的是一个不错的选择;而且它们另一个作用就是可以避免写一些判断null的操作。
class Persion(var name: String, var age: Int) {
override fun toString(): String {
return "{$name, $age}"
}
}
// 调用
var persion1 = Persion("子云心", 30)
persion1.let {// 返回表达式结果
it.age = 15
println("{${it.name}, ${it.age}}")// 结果:{子云心, 15}
}
persion1.run {// 直接能访问到类对象的let版本
println("{${name}, ${age}}")// 结果:{子云心, 15}
}
with(persion1) {// 非扩展函数的run版本
println("{${name}, ${age}}")// 结果:{子云心, 15}
}var persion2 = persion1.also {// 同时返回新的对象的let版本
it.age = 18
}
println(persion2)// 结果:{子云心, 18}var persion3 = persion1.apply { // 同时返回新的对象的run版本
age = 22
}
println(persion1)// 结果:{子云心, 22}
println(persion2)// 结果:{子云心, 22}
println(persion3)// 结果:{子云心, 22}
6.2.2 use自动关闭资源
user高阶函数内部做了很多异常的处理和最后自动close释放资源,所以我们在使用上不需要自己去实现异常捕获和手动close,直接在大括号里使用最后执行的代码逻辑就可以了,也不怕内存泄漏。使用如:
File("build.gradle").inputStream().reader().buffered().use {
println(it.readLines())
}
6.2.3 集合映射函数:filter、map、flatMap以及 asSequence
先来看看这三个函数的作用:
【Android开发者快速上手Kotlin(三) 之 高阶函数和SAM转换】filter:保留满足条件的元素
map:集合中的所有元素一一映射到其他元素构成新集合
flatMap:集合中的所有元素一一映身到新集合并合并这些集合得到新集合
它们的使用如:
val list1: List = listOf(1, 2, 3, 4)val list2 = list1.filter { it % 2 == 0 }
println(list2)// 输出结果:[2,4]val list3 = list1.map { it * 2 }
println(list3)// 输出结果:[2, 4, 6, 8]val list4 = list1.flatMap { 0 until it }
println(list4)// 输出结果:[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
asSequence:转换为懒序列
在集合后加上asSequence后,集合变成为懒序列,只有等到真正需要(调用forEach)时才会被执行,否则它就是一个公式不会被执行。下面来对比一下加了asSequence和没有加asSequence集合的输出结果:
val list: List = listOf(1, 2, 3, 4)list.asSequence()
.filter {
print("filter:$it,")
it % 2 == 0
}.map {
print("map:$it,")
it * 2
}.forEach {
print("forEach:$it,")
}
// 输出结果:filter:1,filter:2,map:2,forEach:4,filter:3,filter:4,map:4,forEach:8,list.filter {
print("filter:$it,")
it % 2 == 0
}.map {
print("map:$it,")
it * 2
}.forEach {
print("forEach:$it,")
}
// 输出结果:filter:1,filter:2,filter:3,filter:4,map:2,map:4,forEach:4,forEach:8,
6.2.4 集合的聚合函数:sum、reduce和fold
先来看看这三个函数的作用:
sum:所有元素求和
reduce:将元素依次按规则聚合,结果与元素类型一致
fold:给定初始化值版本的reduce
它们的使用如:
val list1: List = listOf(1, 2, 3, 4)val list2 = list1.sum()
println(list2)// 输出结果:10val list3 = list1.reduce { acc, i -> acc + i }
println(list3)// 输出结果:10val list4 = list1.fold("Hello") { acc, i -> acc + i }
println(list4)// 输出结果:Hello1234
7 SAM转换 SAM全称是Single Abstract Method,意思是单一抽象方法。Java8中开始对Lambda和SAM转换支持。在使用上为:一个参数类型为只有一个方法的接口的方法时,调用时可用Lambda表达式做转换作为参数。而Kotlin中的SAM转换其实跟Java中的使用差不多,但是得加多一个限制条件,那就是只支持Java接口的Java方法。什么意思?其实就是Kotlin只对调用Java代码支持SAM转换,Kotlin调Kotlin接口方法时是不支持SAM转换的。这是因为语言设计者认为,Kotlin本来就原生支持函数类型,根本没有必要再进行单一接口方法的定义。看看看如何使用:
// Java代码
public interface OnClickListener {
void onClick();
}
// Kotlin调用代码
var onClickListener1 = object : OnClickListener {// 匿名类的调用方式
override fun onClick() {
println("Hello World!")
}
}
var onClickListener2 = OnClickListener {// SAM转换调用方式
println("Hello World!")
}
上述代码,如果要将onClickListener1和onClickListener2两个变量都传递给一个Java方法,可以这样:
// Java代码
public class ListenerManager {
List listenerList = new ArrayList();
public void addListener(OnClickListener listener) {
listenerList.add(listener);
}public void removeListener(OnClickListener listener) {
listenerList.remove(listener);
}
}
// Kotlin调用代码
val listenerManager = ListenerManager()
listenerManager.addListener(onClickListener1)
listenerManager.addListener(onClickListener2)
补充:
ListenerManager的addListener方法因为是Java代码,其实我们在Kotlin中调用时,也可以使用函数类型作为参数传入,如:
var onClickListener3 = {// Kotlin中的函数类型
println("Hello World!")
}
listenerManager.addListener(onClickListener3)
注意:
如果只是一次性调用,上述代码是没有问题的。但是像我们在Android开发中常用的addListener和removeListener中,这样的调用可能就会踩到坑了。因为函数类型在编译时针对每次调用的地方都会new出一个新的匿名类,所以添加一时爽,移除真踩坑:
val listenerManager = ListenerManager()
listenerManager.addListener(onClickListener1)
listenerManager.addListener(onClickListener2)
listenerManager.addListener(onClickListener3)listenerManager.removeListener(onClickListener1)// 可以移除
listenerManager.removeListener(onClickListener2)// 可以移除
listenerManager.removeListener(onClickListener3)// 不可以移除,因为跟add时不是同一个对象
未完,请关注后面文章更新…
推荐阅读
- 快速上手 Kotlin 开发系列之函数与函数嵌套
- 加深学习|android属性动画(Kotlin)
- android|一个简单的Android圆形ProgressBar
- Kotlin专题「十一」(可见性修饰符(private、protected、internal、public))
- Kotlin专题「十」(接口与函数接口(Functional (SAM) interfaces))
- Kotlin专题「十三」(数据类(Data Classes))
- Kotlin专题「十四」(密封类(Sealed Classes))
- Kotlin专题「十二」(扩展Extensions(扩展函数与属性))
- kotlin数字与java数字的不同