Kotlin函数

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”时,结果跟之前是一致的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容