1.函数引用
kotlin中函数引用跟c++中的方法指针很相似,函数引用可以像其他类型的引用一样作为方法的参数和返回值。看一个数组遍历的例子
var array = intArrayOf(1,2,3,4,5,6)
array.forEach(::println)
在kotlin中我们除了用for..in..可以遍历数组外,foreach作为数组的扩展函数一样可以用来遍历,我们先来看看他的定义
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
}
forEach函数接收一个(Int)->Unit类型的函数,返回一个Unit,既然这样我们自己是不是也可以定义一个这个类型的函数作为参数,去遍历处理数组
fun a(i:Int){
println("${i*2}")
}
我们在当前包下面定义了一个函数a,同样是传入一个Int,返回一个Unit,符合(Int)->Unit,作用是将参数加倍输出。
array.forEach(::a)
运行输出跟我们的期望结果一致。那么如果是定义在类中的函数呢?
class B{
fun b(i: Int){
println("${i+1}")
}
}
B类中的b函数类型符合要求,这都一目了然,那么我该怎么给forEach传参呢?
array.forEach(B::b)//error
array.forEach(B()::b)//pass
这是为什么呢?因为B::b这个写法对应的函数类型不是(Int)->Unit,而是(B,Int)->Unit,不相信吗?那我们做个实验
fun test(b: B,i: Int,action:(B,Int)->Unit){
action.invoke(b,i)
}
test(B(),2,B::b)
编译通过,运行输出正常。
关于函数引用先介绍到这里,kotlin中函数引用无处不在。
2.常用高阶函数
1.forEach
forEach函数,作用于集合对象,String等,用来迭代集合对象中的每个元素,来看下函数定义。
public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
参数是(T)->Unit类型的函数,返回值是Unit,函数体中的代码意思是遍历集合对象中的每个元素,并将每个元素作为参数传给函数执行。还是比较简单,很好理解的。用法也很简单,一般可用于集合对象元素输出。
var array = arrayOf("1","2","3","4","5")
array.forEach { println(it) }
2.map
map函数,作用于集合对象,String等,用来迭代操作集合对象中的每个元素,并返回。和forEach函数很相近,来看下函数定义。
public inline fun <T, R> Array<out T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(size), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Array<out T>.mapTo(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
参数是(T)-R类型的函数,返回一个List<R>,那他做了什么?配合mapTo函数,我可以看到,map函数迭代了集合中的每个元素,执行了(T)->R函数,并将执行结果存放在List<R>的临时集合中,迭代完集合后,再将这个List<R>作为返回值返回。
array.map { it.toInt()*10 }.forEach { println(it) }
使用例子也很简单,这行代码的意思是将集合中的元素转化成Int后乘以10后遍历输出。
3.flatMap
flatMap函数,作用于集合对象,String等,作用我们直接来看定义吧
public inline fun <T, R> Array<out T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
return flatMapTo(ArrayList<R>(), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Array<out T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
函数接受一个(T)->Iterable<R>类型的函数,返回一个List<R>,通过查看flatMapTo函数,函数的作用也一目了然了。也是遍历目标集合中的每个元素,把元素作为入参执行传入的函数,返回一个R类型的可迭代对象(可以理解成也是集合),然后将转化后的集合一起加入到一个新临时集合中,待遍历完毕后,将这个临时集合作为返回值返回。感觉有点绕...看例子!
var rangeArray = arrayOf(1..10,20..30,50..80)
rangeArray.flatMap { it }.map { "No.$it" }.forEach { println(it) }
这是一个区间集合,区间本身就是可迭代对象,我们通过flatMap将集合中元素区间整合成一个大集合,然后对象map处理,forEach输出。
4.reduce
reduce函数,作用于集合对象,String等,用来积累。不太好懂。直接看定义吧
public inline fun <S, T: S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
val iterator = this.iterator()
if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
var accumulator: S = iterator.next()
while (iterator.hasNext()) {
accumulator = operation(accumulator, iterator.next())
}
return accumulator
}
入参是(acc:S,T)->S类型的函数,返回S类型,其中S必须是T的父类。函数体中的代码含义是迭代取出每个元素,定义了一个临时积累值,每取出一个元素,都将积累值和元素值作为入参,执行operation函数,最终奖积累值作为返回值返回。
var array = arrayOf("1","2","3","4","5")
var total = array.map { it.toInt() }.reduce { acc, i -> acc+i }
println(total)
还是之前的数组,比之前多了一步累加,用reduce函数做求和非常方便。
var stringArray = arrayOf("Hello","Kotlin","welcome")
var string = stringArray.map { it.toUpperCase() }.reduce { acc, s -> "$acc,$s" }
println(string)
也可以做字符串的拼接。
5.flod
flod的函数跟reduce函数很相似,在比reduce多一个初始值,所以可以这么使用。
var stringArray = arrayOf("Hello","Kotlin","welcome")
var string = stringArray.map { it.toUpperCase() }.fold("Title:"){ acc, s -> "$acc,$s"}
println(string)
但其实差别不止这点,来看下fold函数的定义。
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
var accumulator = initial
for (element in this) accumulator = operation(accumulator, element)
return accumulator
}
除了多了一个初始值外,不同于reduce函数,R与T并不需要有继承关系,我们再试试。
var stringlength = stringArray.map { it.toUpperCase() }.fold(0){acc, s -> acc + s.length}
println(stringlength)
我们改写了上面的例子,计算出了集合拼接字符串后的长度,acc是Int,s是String,得确没有继承关系。例子有点多余,但是能说明fold与readuce的区别。
6.filter
filter函数,顾名思义是用来过滤的,比较简单,我们直接来看函数定义
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
入参是(T)->Boolean类型,返回值是List<T>,作用是迭代对象,将符合条件的元素保留存在临时集合中,迭代结束将临时集合返回。
var array = arrayOf("1","2","3","4","5")
array.filter { it.toInt()%2==0 }.forEach { println(it) }
找出了目标集合中的所有偶数。filter函数简单明了,这里就不多说了。
7.takeWhile
takeWhile函数也是作用于集合对象,String等,来看下函数定义
public inline fun <T> Array<out T>.takeWhile(predicate: (T) -> Boolean): List<T> {
val list = ArrayList<T>()
for (item in this) {
if (!predicate(item))
break
list.add(item)
}
return list
}
入参是(T)->Boolean函数类型,返回值是List<T>类型,作用是迭代集合,符合判断条件的保存起来,直到找到第一个不满足条件的或者迭代结束,最终把临时保存的集合返回。
var array = arrayOf("1","2","3","4","5")
array.takeWhile { it!="3" }.forEach { println(it) }
这个函数也没什么难度,不多讲了。
8.let
之前这么多函数都是针对集合,String这些可迭代对象的,从let开始则没有了使用对象上面的限制了,我们来看下let的定义。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
从定义上就可以看出来,let是T类型的扩展方法,也就是所有类型的扩展方法,入参是(T)->R的函数代码块,返回值是R类型的,函数体就是执行block代码块。这个函数约束很小,极其灵活。
var array = arrayOf("1","2","3","4","5")
var size = array?.let { it.size }
在array非空的情况下,计算array的size,并给变量size赋值。
9.apply
apply函数与let函数很相似,我们直接来看下函数定义。
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
看入参是T.()->Unit类型的,返回值是T类型,看函数体,执行了block代码块并返回当前对象,这意味着在block代码块内部可以使用this,这就是跟let最大的区别,let是把this当作参数传到代码块内部。
array.apply { println(this.size)}.apply { println(this[0]) }.apply { println(this[this.size -1]) }
返回的是对象自己,所以可以这样链式执行。
10.with
with是一个单纯的函数,并不是扩展方法,定义是这样的
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
参数有两个,一个是T类型的receiver,类型是T.()->R的函数代码块,返回值是R类型,函数体就是传入的receiver执行block代码块。
with(array){
this.map { "No.$it" }
}.forEach { println(it) }
例子很无趣,只是单纯为了演示with这个函数。
11.use
use函数作用于现实了Closeable接口的类,比如文件io操作,看下定义
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var closed = false
try {
return block(this)
} catch (e: Exception) {
closed = true
try {
this?.close()
} catch (closeException: Exception) {
}
throw e
} finally {
if (!closed) {
this?.close()
}
}
}
例子:
var l = BufferedReader(FileReader("123.txt")).use {
var line: String = ""
while (true){
line += it.readLine()?: break
}
line
}
println(l)
3.尾递归
尾递归就是调用函数是在函数体尾部的递归。因为是尾部,所以根本没有必要去保存任何局部变量,这样尾递归比一般递归节省开销。在kotlin中还可以用tailrec关键字进一步优化尾递归。
tailrec fun a(n :Int):Int{
if (n == 1)
return 1
return a(n-1)
}
例子没有什么意义,就是单纯介绍下kotlin中尾递归优化。
4.闭包
闭包简单的说就是函数运行时的环境。来看下下面的例子
fun a(init:Int):()->Int{
var s = 2
return fun():Int{
return init + s++
}
}
作为返回值返回的匿名函数中使用了外部定义的变量s。这种方式就是闭包的体现。闭包就是能够读取其他函数内部变量的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。
我们来使用下函数a:
fun main(args: Array<String>) {
var a = a(10)
println(a())
println(a())
println(a())
println(a())
println(a())
}
打印会是什么呢?12,13,14,15,16,并不是12,12,12,12,12.这说明s这个变量被保存在了内存中。
5.函数复合
函数复合就是将两个函数组合成一个函数的编码方式,直接看例子
fun b(i:Int):Int{
return i*2
}
fun c(i: Int):Int{
return i*i
}
这么调用:
println(c(b(2)))
现在我们要将b和c函数组合成一个新函数bc
infix fun Function1<Int,Int>.andThen(function:Function1<Int,Int>):Function1<Int,Int>{
return fun(i:Int):Int{
return this.invoke(function(i))
}
}
这么调用:
var bc = ::c andThen ::b
println(bc(2))
我们将这个函数用范型的方式定义:
infix fun <P1,P2,R> Function1<P1,R>.andThen2(function:Function1<P2,P1>):Function1<P2,R>{
return fun(p2:P2):R{
return this.invoke(function(p2))
}
}
infix fun <P1,P2,R> Function1<P1,R>.andThen3(function:Function1<P2,P1>):Function1<P2,R> = fun (p2:P2):R = this.invoke(function(p2))
这么调用:
var bc = ::c andThen ::b
println(bc(2))
上面两种方式定义是一样的,都要看得懂。关于函数复合就简单介绍下。
6.函数柯里化
fun log(tag:String,level:Int,message:String){
println("$tag-$level,$message")
}
fun log2(tag: String)=fun(level:Int)=fun(message:String)=println("$tag-$level,$message")
log函数到log2函数就可以叫做函数的柯里化,那使用的角度有什么区别呢?
log("1",2,"3")
log2("1")(2)("3")
7.偏函数
偏函数就是函数的半成品,跟柯里化函数有点关系,我们将柯里化的例子修改下。
var log3 = log2("1")(2)
log3("3")
我们将log2这个柯里化函数只参入两个参数得到一个函数变量,这个函数就是原函数的偏函数,当它传入“3”时,结果跟之前是一致的。
网友评论