Kotlin中的高阶函数

基本概念

kotlin中的函数比java中的函数高级,它可以作为参数进行传递, 也可以作为返回值返回

fun main(args: Array<String>) {
    args.forEach(::println)
}

这个函数之所以能运行,是因为forEach中的参数是action: (T) -> Unit),一个参数T返回Unit的函数

public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

而println刚符合这个条件

public inline fun println(message: Any?) {
    System.out.println(message)
}

::println是一个包级函数的引用,下面我们看一个示例:
定义一个

class Hello{
    fun world(){
        println("Hello World")
    }
}

可以赋值

val hello = Hello::world

下面再来看看

args.filter(String::isNotEmpty)

过滤掉非空的字符串,filter的实现如下

public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

参数为predicate: (T) -> Boolean,但是isNotEmpty并没有参数

@kotlin.internal.InlineOnly
public inline fun CharSequence.isNotEmpty(): Boolean = length > 0

这是因为包级函数,有一个默认的参数就是它类本身,如上面isNotEmpty有一个默认的参数就是
CharSequence.
接下来我们再定义一个类

class PdfPrinter{
    fun println(any: Any){
        kotlin.io.println(any)
    }
}

下面这样引用是不行的
fun main(args: Array<String>) {
    args.forEach(PdfPrinter::println)
}

原因就是之前提到的,有一个默认的参数就是它类本身,就是PdfPrinter,如果我们去实例化对象调用是可以的

fun main(args: Array<String>) {
    val pdfPrinter = PdfPrinter()
    args.forEach(pdfPrinter::println)
}

常见的高阶函数

map

下面我们做一个集合的映射,通常我们做法是这样.

fun main(args: Array<String>) {
    val list = listOf(1,3,4,5,10,8,2)
    val newList = ArrayList<Int>()
    list.forEach {
        val element = it * 2 + 3
        newList.add(element)
    }
    newList.forEach (::println)
}

然后使用高阶函数map去做会简单很多

fun main(args: Array<String>) {
    val list = listOf(1,3,4,5,10,8,2)

    val newList = list.map { it*2+3 }
    
    newList.forEach (::println)
}

flatMap

fun main(args: Array<String>) {
    val list = listOf(1..20,
                      2..5,
                      100..322)
    val flatList = list.flatMap {
        it.map {
            "No.$it"
        }
    }
    flatList.forEach(::println)
}

如果觉得it参数过多,难于理解可以加入参数名

fun main(args: Array<String>) {
    val list = listOf(1..20,
                      2..5,
                      100..322)

    val flatList = list.flatMap { intRange ->
        intRange.map { intElement ->
            "No.$intElement"
        }
    }

    flatList.forEach(::println)
}

这样更加容易理解.

reduce

迭代求和

println(flatList.reduce{
        acc,i -> acc+i
})

求阶乘

fun factorial(n : Int):Int{
    if (n == 0) return 1
    return (1..n).reduce(){acc, i -> acc * i }
}
 (0..6).map(::factorial).forEach(::println)

fold

这个函数和reduce类似,不过它可以设置一个初始值
接着上面的函数累计求和(它有一个初始值5)

(0..6).map(::factorial).fold(5){acc, i ->  acc+i }

另外我们还可以使用fold做字符串的连接

(0..6).map(::factorial).fold(StringBuilder()){ acc, i ->  acc.append(i).append(",") }

joinToString

这个函数也是用来做字符串的连接

(0..6).joinToString(",")

filter

过滤基数

val result = (0..6).map(::factorial).filter { it % 2 == 1}

filter函数还有一些其它版本,例如filterIndexed

(0..6).map(::factorial).filterIndexed {index , i-> index%2 == 1}

求基数位的结果.

takeWhile

遇到第一个不符合条件的就结束,留下前边的作为一个新的集合返回.

(0..6).map(::factorial).takeWhile { it % 2 == 0 }

这个表示没有取到偶数的时候就结束提取.

let

接下来我们定义一个Worker

data class Worker(val name : String, val age : Int)

再定义一个findWorker

fun findWorker():Worker?{
    return null
}
fun main(args: Array<String>) {
    val worker = findWorker()
    println(worker?.name)
    println(worker?.age)
}

我们去打印worker的name和age,这种打印方法每次都需要加入?判断是否为null,我们可以利用let函数使其更加简洁.

fun main(args: Array<String>) {
    findWorker()?.let { worker ->
        println(worker.name)
        println(worker.age)
    }
}

因为Worker是data class我们还可以用下面来表示

findWorker()?.let { (name,age) ->
        println(name)
        println(age)
    }

定义函数的调用也是可以的

findWorker()?.let { worker ->
    worker.work()
}

apply

使用apply可以使得上面的表达式更加简洁

fun main(args: Array<String>) {
    findWorker()?.apply {
        work()
        println(age)
    }
}

with

与apply类似,不过with是调用.

fun main(args: Array<String>) {
    val br = BufferedReader(FileReader("hello.txt"))
    with(br){
        var line : String?
        while (true){
            line = readLine()?:break
            println(line)
        }
        close()
    }
}

use

使用use调用使得上面表示更加简洁

BufferedReader(FileReader("hello.txt")).use {
        var line : String?
        while (true){
            line = it.readLine()?:break
            println(line)
        }
}

伪递归优化

data class ListNode(val value : Int, var next: ListNode?)

定义一个函数查找特定的节点

fun findListNode(head : ListNode?, value: Int):ListNode?{
    head?:return null
    if (head.value == value) return head
    return findListNode(head.next,value)
}

当我们return的时候调用的是函数本身,这种情况我们就叫做尾递归.
我们定义一个阶乘的算法

fun factorial(n : Long) : Long{
    return n* factorial(n - 1)
}

因为返回n* factorial(n – 1),这里有个n,并不是函数本身,这就不是尾递归.
如果我们想要优化尾递归,使其变为迭代可以加上关键字tailrec.

tailrec fun findListNode(head : ListNode?, value: Int):ListNode?{
    head?:return null
    if (head.value == value) return head
    return findListNode(head.next,value)
}

闭包

闭包的定义:

  • 函数的运行环境
  • 持有函数的运行状态
  • 函数内部可以定义函数
  • 函数内部可以定义类
    下面定义一个函数
fun makeFun():()->Unit{
    var count = 0
    return fun(){
        println(++count)
    }
}

然后进行调用

fun main(args:Array<String>){
    val x = makeFun()
    x()
    x()
    x()
    x()
    x()
}

打印的值是

1
2
3
4
5

这种情况函数嵌套函数并且保存变量count的情况就叫做闭包.

函数复合

下面我们定义

val add5 = {i : Int -> i + 5}
val multiplyBy2 = {i : Int -> i * 2}
fun main(args:Array<String>){
    println(multiplyBy2(add5(8)))
}

我们可以写一个函数去转换这种调用

infix fun <P1,P2,R> Function1 <P1,P2>.andThen(function: Function1<P2,R>) : Function1<P1,R>{
    return fun(p1 : P1):R{
        return function.invoke(this.invoke(p1))
    }
}

fun main(args:Array<String>){
    val add5AndMultiplyBy2 = add5 andThen multiplyBy2
    println(add5AndMultiplyBy2(8))
}

这就叫函数复合.

柯里化

柯里化用来拆分函数

fun log(tag : String,target: OutputStream, message: Any?){
    target.write("[$tag]$message\n".toByteArray())
}

科里化后的结果

fun log(tag : String) = fun(target : OutputStream) = fun(message : Any?) = target.write("[$tag]$message\n".toByteArray())
fun main(args: Array<String>) {
    log("benny",System.out,"HelloWorld")
    log("benny")(System.out)("HelloWorld Again")
}

调用后结果相同

最后

在这里我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案做成了文档和架构视频资料免费分享给大家【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

资料获取方式:加入Android架构交流QQ群聊:513088520 ,进群即领取资料!!!

点击链接加入群聊【Android移动架构总群】:加入群聊

《Kotlin中的高阶函数》 资料大全

    原文作者:Android架构
    原文地址: https://www.jianshu.com/p/8d633e9ba1f3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞