Kotlin高阶函数

1、高阶函数定义

函数的参数接收的是另一个函数,或者返回值是另一个函数类型,我们把这类函数称为高阶函数
 

2、函数类型

字符串的类型用String表示,整型用Int表示,那么函数的类型呢?

// 参数block接收的是函数类型,该函数具体是无参,返回值为Unit的函数
fun start(block: () -> Unit) {
}

// 参数block接收的是函数类型,该函数具体是有一个String类型参数,返回值为Boolean的函数
fun start(block: (String) -> Boolean) {
}

 

3、Lambda表达式

定义一个高阶函数

// function函数,参数需要两个Int,返回值也是Int的类型
fun number(num1: Int, num2: Int, function: (Int, Int) -> Int): Int {
    return function(num1, num2)
}

通过Lambda的形式调用高阶函数(较为常用)

 // Lambda最后一行代码的返回值作为函数的返回值返回
val result1 = number(5, 10) { num1, num2 ->
    num1 + num2
}
val result2 = number(10, 8) { num1, num2 ->
    num1 * num2
}

 

4、函数引用方式调用
// 顶层函数引用,forEach的参数函数类型是(T) -> Unit,其中T表示的泛型就是list存储的类型String
// 而函数print的参数是any,返回是Unit,符合forEach需要的函数类型,因此通过::形式表示引用print函数
val list = arrayListOf<String>()
list.forEach(::print)

// 引用同一个类中的test函数
fun main(args: ArrayList<String>) {
    val list = arrayListOf<String>()
    list.forEach(::test)
}
private fun test(content: String) {
    println(content)
}
// 扩展函数引用
// filter的参数函数类型是:(T) -> Boolean,而String的扩展函数isNotEmpty的参数是(),返回值是Boolean。
// 看着类型都匹配不上,为什么没问题?由于T是String,所以filter实际上的需要的函数类型是(String) -> Boolean
// 而扩展函数有一个隐藏参数,就是调用者。因此isNotEmpty实际的参数是(String),返回值是Boolean,因此两者是对得上的。
val list = arrayListOf<String>()
list.filter(String::isNotEmpty)

// 其他类里的函数引用,和扩展函数的引用一样,通过类名::函数名,并且同样也有一个隐藏参数,就是调用者,即Animal对象
class Animal {
    fun isDog(): Boolean {
        return true
    }
}
fun main(args: ArrayList<String>) {
    val list = arrayListOf<Animal >()
    list.filter(Animal::isDolg)
}
// 类实例的函数引用,这种情况下,因为是实例化,所以就没有隐藏参数一说
class Animal {
    fun isCat(name: String): Boolean {
        return name == "Cat"
    }
}
fun main(args: ArrayList<String>) {
    val list = arrayListOf<String>()
    // filter需要的函数类型是(String) -> Boolean
    val animal = Animal ()
    list.filter(animal::isCat)
}

 

5、常见的高阶函数
  • Iterable.map
/**
 - map,对元素遍历处理和转换
*/
fun main() {
    val stringList = arrayListOf<String>()
    val intList = stringList.map {
        it.toInt()
    }
    val newList = intList.map {
        it * 5 + 10
    }
}
  • Iterable.flatMap
fun main() {
    val list = arrayListOf<ArrayList<Int>>()
    // newList是List类型
    val newList = list.flatMap { innerList ->
        innerList.map { element ->
            element.toString()
        }
    }
    // newList2是List类型
    val newList2 = list.flatMap {
        it
    }
}
  • filter
// 筛选出符合条件的元素,添加到一个新的数组
val array = intArrayOf(1, 2, 8, 11, 22, 55, 66)
val newArrayList = array.filter {
	it % 2 == 0
}
  • any & all & none
// result=true,any表示是否有其中一个满足Lambda表达式,即int数组中是否有一项it%5==0
val array = intArrayOf(1, 2, 8, 11, 22, 55, 66)
val result = array.any {
	it % 5 == 0
}

// result=false,all表示是否所有项都满足Lambda表达式
val array = intArrayOf(1, 2, 8, 11, 22, 55, 66)
val result = array.all {
	it % 5 == 0
}

// result=ture,none表示是否所有项都不满足Lambda表达式
val array = intArrayOf(1, 2, 8, 11, 22, 55, 66)
val result = array.none {
	it % 9 == 0
}
  • takeWhile
// result=[1],遍历找出符合条件,和filter有点像,但是filter不同地方在filter总是会遍历当前IntArray的所有元素,
// 而takeWhile在第一次发现predict不满足的时候就不再遍历,后面的元素即使满足条件也不会加入到结果中。
val array = intArrayOf(1, 2, 8, 11, 22, 55, 66)
val result = array.takeWhile {
	it % 2 != 0
}
println(result)
  • reduce 和 fold

两个函数都是对集合的遍历,遍历完成之后能得到一个结果。
reduce的返回值类型必须和集合的元素类型相符。
fold的返回值类型则不受约束,并且有一个初始值。

fun main(args: Array<String>) {
    val list = listOf(1, 2, 3, 4, 5)
    // 1到5求和,acc是累加的返回值,i是当前遍历列表中的值
    println(list.reduce { acc, i -> acc + i }) 
}

// 求阶乘
fun factorial(n: Int): Int { 
    if (n == 0) return 1
    return (1..n).reduce { acc, i -> acc * i }
}

// result=1,2,8,11,
val array = intArrayOf(1, 2, 8, 11)
val result = array.fold(StringBuilder()) { sb, i ->
	sb.append(i)
	sb.append(",")
}
println(result)
  • apply & let & run & with
var list: ArrayList<Int>? = ArrayList()
@Test
fun test() {
	// this指向的就是list,并且返回的oldList还是原来的list
    val oldList = list?.apply {
        this.add(5)
        this.add(10)
        this.add(20)
    }
    println(oldList.toString())

    // it指向的是list,Lambda表达式的最后一行表示返回值
    val addResult = list?.let {
        it.add(30)
        it.add(50)
        true
    }
    println(addResult)

    // 相当于apply和let的组合体,this指向的是list,Lambda表达式的最后一行表示返回值
    val addResultSize = list?.run {
        this.add(60)
        this.size
    }
    println(addResultSize)

    // 和run函数一样,区别是run是扩展函数,而with是普通函数,调用方式不同
    val addResultSize2 = with(list!!) {
        this.add(80)
        this.size
    }
    println(addResultSize2)
}
  • use

use函数会自动关闭调用者(无论中间是否出现异常),Kotlin的File对象和IO流操作变得行云流水
use函数内部实现也是通过try-catch-finally块捕捉的方式,所以不用担心会有异常抛出导致程序退出
close操作在finally里面执行,所以无论是正常结束还是出现异常,都能正确关闭调用者

BufferedReader(FileReader("build.gradle")).use {
    var readLine: String?
    while (true) {
        readLine = readLine() ?: break 
        println(readLine)
    }
}

 

6、递归及尾递归优化

尾递归:函数的末尾递归调用函数自己

@Test
fun test() {
   // 5+4+3+2+1
   println(add(5))
   // 会抛出StackOverflowError异常
   println(add(100000))
}

private fun add(num: Int): Int {
   if (num == 1) {
       return 1
   }
   return num + add(num - 1)
}

要理解这个问题,先回顾下方法的执行过程。一个应用程序的执行实际上可以看做是一个个方法入栈出栈的过程。当调用一个方法的时候,会将该方法入栈,当方法执行完毕后,就会执行出栈操作。这个栈可以被称为方法栈,方法栈是有长度限制的(实际上栈也是一块内存区域),当我们入栈的方法长度超过了方法栈的最大限制就会抛出StackOverflowError异常。

那为什么我们平时很少碰到这个异常?这是因为,正常的方法调用都会在该方法执行完成后被清理出栈,因此栈长度一般都会在最大的长度范围之内。

那么尾递归怎么优化呢?关键字tailrec
tailrec修饰的函数,其未递归返回的需要是一个函数整体add(num - 1, count + num),而不能是num + add(num - 1)。
另外,try catch finally异常块里,不能使用tailrec关键字修饰函数

@Test
fun test() {
    // 5+4+3+2+1
    println(add(100000, 0))
}

private tailrec fun add(num: Int, count: Long): Long {
    if (num == 0) {
        return count
    }
    return add(num - 1, count + num)
}

 

7、闭包

函数里面声明函数,函数里面返回函数,就是闭包
闭包是能够读取其他函数内部变量的函数

@Test
fun test() {
    // 变量f指向count函数里的匿名函数
    val f = count()
    // 多次调用该匿名函数
    f()
    f()
}

private fun count(): () -> Unit {
    var conut = 0
    // 返回一个匿名函数,这个函数持有count的状态
    return fun() {   
        println(++conut)
    }
}

你可能感兴趣的:(kotlin,高阶函数,kotlin)