天天看点

Kotlin(四)之函数函数式编程定义形参函数高阶函数Lambda表达式匿名函数内联函数特殊函数

文章目录

  • 函数式编程
  • 定义
  • 形参
  • 函数
  • 高阶函数
  • Lambda表达式
  • 匿名函数
  • 内联函数
  • 特殊函数
    • run
    • apply
    • let
    • also
    • With

函数式编程

  1. 函数式编程是关于不变性和函数组合的编程范式,具有如下特征
    • 一等函数支持 Cfirst-class function) : 函数也是一种数据类型,可以作为参数传入另一个函数中,同时函数也可以返回一个函数。
    • 纯函数( pure function)和不变性( immutable) : 纯函数指的是没有副作用的函数 (函数不去改变外部的数据状态)。函数式编程是转换数据而非修改原始数据
    • 函数的组合 (compose function) :在面向对象编程中是通过对象之间发送消息来构建程序逻辑的;而在函数式编程中是通过不同函数的组合来构建程序逻辑的。

定义

  1. 定义
    fun 函数名(形参列表)[:返回值类型]{
      函数体
    }
               
  2. 如果没有返回值
    • 直接忽略":返回值类型"部分
    • 使用":Unit"指定返回Unit代表没有返回值,Kotlin的Unit相当于java的void(但是并不是一样)。如果函数没有返回值,编译器会为函数添加一个默认的返回值,类型是

      kotlin.Unit

    fun min(a: Int, b: Int) {
    
    }
    
    fun min2(a: Int, b: Int): Unit {
    
    }
    
    fun min3(a: Int, b: Int): Unit {
        return Unit
    }
    
    fun main() {
        /**
         * 输出
         * 1 
         * kotlin.Unit
         */
        var value = run(1)
        var value1 = run1(1)
        var value2 = run2(1)
        println(value)
        println(value1)
        println(value2)
    
    }
    
    fun run(a: Int) {
        println(a)
    }
    fun run1(a: Int): Unit {
        println(a)
    }
    
    fun run2(a: Int): Unit {
        println(a)
        return Unit
    }
               
  3. Unit

    源码,从源码可以看出

    Unit

    是一个真正的类
    public object Unit {
        override fun toString() = "kotlin.Unit"
    }
               
  4. 在Kotlin中由于有Unit类型,对于不想实现的接口方法,可以更简洁
    interface Base {
        fun test(): Unit
    }
    
    class Child : Base {
        override fun test() = Unit
    }
               
  5. 函数定义实例,return语句可以返回常量、变量或者表达式
    /**
     * 定义一个函数,有两个形参,返回Int
     */
    fun max(x: Int, y: Int): Int {
        val result = if (x > y) x else y
        return result
    }
    
    fun max2(x: Int, y: Int): Int {
        return if (x > y) x else y
    }
               
  6. 单表达式函数:函数只有一条表达式,可以省略括号,使用=代替。对于单表达式函数,编译器可以推断出返回值(即可以省略返回值类型)
    fun area0(x: Double, y: Double): Double {
        return x * y
    }
    以下两种方式等价上面
    fun area1(x: Double, y: Double): Double = x * y
    
    fun area2(x: Double, y: Double) = x * y
    
               
  7. 在java中没有顶级函数和顶级变量,java的所有变量和方法都必须被封装在类中。kotlin中函数必须有fun关键字,因为Kotlin并不是一个完全的面向对象的编程语言,并不是所有的元素都必须被封装在类型中,如果没有fun,编译器无法确定所声明的是不是函数。

形参

  1. 命名参数:Kotlin允许调用函数时通过名字来传入参数值
    fun max(x: Int, y: Int): Int {
        val result = if (x > y) x else y
        return result
    }
    
    fun main(args: Array<String>) {
      	//传统调用函数的方式,根据参数位置传入参数值
        val max = max(5, 6)
        //根据参数名传递参数,并且不需要按照函数原有形参的顺序
        val max2 = max(x = 5, y = 6)
        val max3 = max(y = 6, x = 5)
        //部分使用命名参数, 在命名参数后面的只能是命名参数,不能这样写max(x=5,6)
        val max4 = max(5, y = 6)
        println(max)
        println(max2)
        println(max3)
    
    }
               
  2. 形参默认值:Kotlin中,在定义函数时可以为一个或多个形参指定默认值。Kotlin建议将带默认值的参数放在函数形参列表的后面
    fun sayHello(name: String = "jannal", message: String = "欢迎") {
        println("hello ${name},${message}")
    }
    
    fun main(args: Array<String>) {
      	//全部使用默认形参,输出:hello jannal,欢迎
        sayHello();
        //message使用默认值,输出:hello jack,欢迎
        sayHello("jack");
        //都不使用默认值,输出:hello jack,welcome
        sayHello("jack", "welcome");
        //指定参数不使用默认值,没指定的使用默认值,输出:hello jannal,welcome
        sayHello(message = "welcome")
    }
               
  3. 尾递归函数:当函数将调用自身作为它执行的最后一行代码,且递归调用后没有更多代码时,可使用尾递归语法。尾递归不能在异常处理的try、catch、finally块中使用,尾递归函数需要使用tailrec修饰。与普通递归相比,编译器会对尾递归进行修改,将其优化成一个快速而高效的基于循环的版本,这样就可以减少可能对内存的消耗
    fun fact(n: Int): Int {
        if (n == 1) {
            return 1
        } else {
            return n * fact(n - 1)
        }
    }
    
    tailrec fun factRec(n: Int, total: Int = 1): Int = if (n == 1) total else factRec(n - 1, total * n)
               
  4. 可变形参: kotlin 允许个数可变的形参可以处于形参列表的任意位置,但是kotlin要求一个函数最多只带一个个数可变的形参。
    • 如果需要给可变参数后面的参数传入参数值,则必须使用命名参数。
    • 如果已有一个数组,希望将数组的多个元素传入可变参数,则可以在传入的数组参数前添加

      "*"

      运算符
    fun testVarArgs(a: Int, vararg books: String) {
        for (book in books) {
            println(book)
        }
        println(a)
    }
    fun main(args: Array<String>) {
        
        testVarArgs(3, "java", "C", "C++")
        //对于已有数组使用* 运算符
        var arr = arrayOf("java", "C", "C++")
        testVarArgs(3, *arr)
    }
               

函数

  1. 重载:与java一样,Kotlin的函数重载也只能通过形参列表进行区分,即形参个数不同、类型不同的都可以算函数重载。如果重载的函数中包含了可变参数,Kotlin会尽量执行最精确的匹配,大部分时候不建议重载形参个数可变的函数,容易出错。
    fun testOverride(msg: String) {
        println("一个参数")
    }
    
    fun testOverride(vararg msg: String) {
        println("可变参数")
    }
    
    fun main(args: Array<String>) {
        //输出:可变参数
        testOverride()
        //输出:一个参数
        testOverride("a")
        //输出:可变参数
        testOverride("a", "b")
    }  
    
               
  2. 局部函数: 在函数体内部定义的函数称为局部函数。默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭函数内有效,其封闭函数可以返回局部函数,以便在其他作用域中使用局部函数
    /**
     * 局部函数:在函数体内定义函数
     */
    fun operator(func: String, x: Int, y: Int): Int {
        fun add(x: Int, y: Int): Int {
            return x + y
        }
    
        fun sub(x: Int, y: Int) = x - y
    
        fun multi(x: Int, y: Int) = x * y
        var result: Int = 0
        when (func) {
            //调用局部函数
            "add" -> {
                result = add(x, y)
            }
            "sub" -> {
                result = sub(x, y)
            }
            "multi" -> {
                result = multi(x, y)
            }
        }
        return result;
    }
    
    fun main(args: Array<String>) {
        println(operator("add", 1, 2))
        println(operator("sub", 1, 2))
        println(operator("multi", 1, 2))
    }      
               

高阶函数

  1. 高阶函数: Kotlin中函数本身有自己的类型。函数类型与数据类型一样,即可用于定义变量,也可用于作函数的形参类型,还可以作为函数的返回值类型
  2. 函数类型的定义: 【函数的形参列表 -> 返回值类型组成】
    //定义函数
    fun add(x: Int, y: Int): Int {
        return x + y
    }
    
    //定义一个变量,其类型为(Int,Int)->Int
    var myfunc: (Int, Int) -> Int
    
    //将函数add赋值给myfunc,则myfunc可以当做add使用
    myfunc = ::add
    
    /**
     * 只要被赋值的函数类型与myfunc的变量类型一致,就可以被赋值成功
     * 通过使用函数类型的变量,可以让myfunc在不同的时间指向不同的函数,从而使程序更加灵活
     */
    myfunc = ::sub
    
    println(myfunc(2, 3))
     
               
  3. 如果要在函数中传入函数,需要在函数中定义函数类型的形参。Kotlin支持像使用其他类型一样使用函数类型
    /**
     * 在函数参数上使用函数类型
     */
    fun add(x:Int,y: Int,fn:(Int,Int)->Unit):Int{
        fn(x,y);
        return x + y
    }
    fun myprint(x:Int,y:Int){
        println("x:${x},y:${y}")
    }
    
    //x:2,y:3
    add(2,3,::myprint)
               
  4. 使用函数类型作为返回值
    fun myprint(x:Int,y:Int){
        println("x:${x},y:${y}")
    }
    //使用函数类型作为返回值
    fun add(x:Int,y:Int,z:Int):(Int,Int)->Unit{
        val result =  x+y+z;
        return ::myprint
    }
    var printFunc = add(2,3,4)
    //x:2,y:3
    printFunc(2,3)
               
  5. 函数类型声明的箭头表达式有时过于冗长,显得不够简洁,此时可以通过Kotlin类型别名(typealias )来简化
    val sourceFunc = fun(a: (String) -> Int, b: (Int) -> Boolean): (String) -> Boolean {
        return { b(a(it)) }
    }
    
    typealias A = (String) -> Int
    typealias B = (Int) -> Boolean
    typealias C = (String) -> Boolean
    //简化后的,有点像数学公式
    val simpleFunc = fun(a: A, b: B): C {
        return { b(a(it)) }
    }
               

Lambda表达式

  1. 使用Lambda表达式代替局部函数
    fun operator2(func: String): (Int, Int) -> Int {
        when (func) {
        //使用lambda代替局部函数,返回lambda表达式
            "add" -> return { m: Int, n: Int ->
                m + n
            }
            "sub" -> return { m: Int, n: Int ->
                m - n
            }
            "multi" -> return { m: Int, n: Int ->
                m * n
            }
            else -> return { m: Int, n: Int ->
                m / n
            }
        }
    }
    
               
  2. lambda表达式与局部函数的区别
    • Lambda表达式总是被大括号括着
    • 定义lambda表达式不需要fun关键字,无需指定函数名
    • 形参列表(如果有的话)在->之前声明,参数类型可以省略
    • 函数体(lambda表达式执行体)放在->之后
    • 函数的最后一个表达式自动被作为lamdba表达式的返回值,无需使用return
  3. lambda的语法
    {(形参列表) ->
      //执行语句
    }
               
  4. lambda表达式的本质是功能更灵活的代码块,即完全可以将lambda表达式赋值给变量或直接调用Lambda表达式。
    //省略形参名,使用it代表形参
        var square: (Int) -> Int = { it * it }
        //16
        println(square(4))
    
        //多个形参无法省略
        var result = { x: Int, y: Int ->
            var result = x + y
            result
        }(4, 3)
        println(result)
               
  5. 如果函数的最后一个参数是函数类型,而且如果要传入一个Lambda表达式作为相应的参数,那么就允许在圆括号之外指定Lambda表达式(尾随闭包 Tail Closure)。如果Lambda表达式是函数调用的唯一参数,则调用方法时的圆括号完全可以省略,通常建议将函数类型的形参放在形参列表的最后,这样就可以方便传入lambda表示作为参数。
    var list = listOf("java", "Kotlin", "GO")
        var dropWhile = list.dropWhile() { it.length > 3 }
        //[GO]
        println(dropWhile)
        dropWhile = list.dropWhile { it.length > 3 }
        //[GO]
        println(dropWhile)
               
  6. 如果一个函数既包含个数可变的形参,也包含函数类型的形参,那么就应该将函数类型的形参放在最后

匿名函数

  1. Lambda表达式有一个严重的缺陷,无法指定返回值类型。大部分时候kotlin可以推断出lambda表达式返回的类型。在一些特殊场景下无法推断返回值类型,此时就需要显示指定返回值类型,而匿名函数可以代替lambda
//定义匿名函数,并赋值给变量
    var test = fun(x: Int, y: Int): Int {
        return x + y
    }
    println(test(2, 4))

           
  1. 如果系统可以推断出匿名函数的形参类型,那么匿名函数允许省略形参类型
    //使用匿名函数作为filter的参数
        var filterList = listOf(3, 4, 5, 6, 30, 40, 50).filter(
                fun(el): Boolean {
                    return el > 10
                }
        )
    
        println(filterList)   
               
  2. 匿名函数使用单表达式作为函数体,无须指定返回值类型
    //定义匿名函数,函数体是单表达式,所以可以省略函数的返回值类型
        var sub = fun(x: Int, y: Int) = x + y
               
  3. 匿名函数的本质是函数,即匿名函数的return返回该函数本身,而Lambda表达式的return用于返回它所在的函数,而不是lambda表达式
    //匿名函数返回该函数本身,所以对外层函数a()没有影响
    fun a() {
        var list = listOf(2, 3, 4, 5)
        list.forEach(fun(n) {
            print("${n}")
            return
        })
    }
    //2345
    a()
      
      
    //lambda表达式返回它所在的函数,对b()有影响
    fun b() {
        var list = listOf(2, 3, 4, 5)
        list.forEach({ n ->
            print("${n}")
            return
        })
    }
    //2
    b()
      
      
    fun c() {
        var list = listOf(2, 3, 4, 5)
        list.forEach({ n ->
            print("${n}")
            //使用限定返回,此时return返回lambda表达式
            return@forEach
        })
    }
    //2345
    c()
               
  4. Lambda表达式或匿名函数(以及局部函数、对象表达式)可以访问或修改其所在上下文(闭包)中的变量或常量,这个过程被称为捕获。即使定义这些变量和常量的作用域已经不存在了,Lambda表达式或匿名函数也依然可以访问或者修改他们
    fun makeList(ele: String): () -> List<String> {
        var list: MutableList<String> = mutableListOf()
    
        /**
         * 匿名函数会持有一个其所捕获的变量的副本
         * 1. 从表面上看 addElement访问的是makeList函数的list集合变量
         *    但是程序返回一个新的addElement函数,addElement函数就会自己持有一个新的
         *    list的副本。
         */
    
        fun addElement(): List<String> {
            list.add(ele)
            return list
        }
        return ::addElement
    }
    
    
     var add1 = makeList("jannal")
     //[jannal]
     println(add1())
     //[jannal, jannal]
     println(add1())
     var add2 = makeList("jack")
     //[jack]
     println(add2())
     //[jack, jack]
     println(add2())
    
               

内联函数

  1. 调用lambda表达式或函数的过程是:程序要将执行顺序转移到被调用表达式或函数所在的内存地址,当被调用表达式或函数执行完后,再返回到原函数执行的地方。在这个转移过程中,系统要处理如下的事情
    • 为被调用的表达式或函数创建一个对象
    • 为被调用的表达式或函数所捕获的变量创建一个副本
    • 在跳转到被调用的表达式或函数所在的地址之前,要先保护现场并记录执行地址;从被调用的表达式或函数地址返回时,要先恢复线程,并按原来保存的地址继续执行(即压栈和出栈)
  2. 函数调用会产生一定的时间和空间开销,如果被调用的表达式或函数的代码量本身不大,而且该表达式或函数经常被调用,那么这个时间和空间的开销就很大
  3. 为了避免产生函数调用的过程,可以考虑直接把被调用的表达式或函数的代码嵌入到原来的执行流中。即编译器去负责复制和粘贴,此时相当于没有调用任何函数。
  4. inline关键字修饰的函数就是内联函数,noinline用于修改函数中某一个或者几个函数类型的形参不被内联化。
  5. 如果被调用的Lambda表达式或函数的代码两非常大,且该Lambda表达式或函数多次被调用,每一次调用,该Lambda表达式或函数的代码就会被复制一次,因此会带来程序代码的急剧增加。即如果被调用的Lambda表达式或函数只包含非常简单的代码(比如单表达式),那么应该使用内联函数,否则不应该不使用。
    inline fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
        var result = Array<Int>(data.size, { 0 })
    
        for (i in data.indices) {
            result[i] = fn(data[i])
        }
        return result
    }
    
    
    fun main(args: Array<String>) {
        var arr = arrayOf(10, 20, 30, 40)
        var result = map(arr, { it + 3 })
        println(result.contentToString())
    }
    编译后只会产生一个FunctionInlineKt.class,不会生成其他额度的内部类class文件
    
    map函数被编译后实际变成了
    fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
        var result = Array<Int>(data.size, { 0 })
    
        for (i in data.indices) {
            result[i] = data[i]+3
        }
        return result
    }
               
  6. 在默认情况下,在Lambda表达式中并不允许直接使用return。因为如果是非内联的Lambda表达式,该Lambda表达式会额外生成一个函数对象,因此这种表达式中的return不可能用于返回它所在的函数。由于内联的Lambda表达式会被直接复制、粘贴到调用它的函数中,故此时在该Lambda表达式中可以使用return,该return就像直接写在Lambda表达式的调用函数中一样。因此该内联的Lambda表达式中的return可用于返回它所在的函数,这种返回被称为非局部返回。
    inline fun eachPrint(data: Array<Int>, fn: (Int) -> Unit) {
        for (el in data) {
            fn(el)
        }
    }
    
    fun main(args: Array<String>) {
        var arr = arrayOf(10, 20, 30, 40)
        eachPrint(arr, {
            print(it)
            /**
             *1. 如果eachPrint没有inline修饰,此处会编译错误,即非内联的lambda表达式无法使用return
             *2. 有inline修饰,则返回的是main函数
             */
            return
        })
    }
    
               

特殊函数

  1. kotlin-stdlib-common-1.3.31.jar

    中的

    kotlin/StandardKt

run

  1. 定义:传入代码块,然后返回代码块结果
    @kotlin.internal.InlineOnly
    public inline fun <R> run(block: () -> R): R {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        return block()
    }
               
  2. 案例
    fun main() {
        //输出:打印结果
        queryData()
        //输出:打印结果
        run({ queryData() })
        //输出:打印结果
        run { queryData() }
    }
    
    fun queryData(): String {
        println("打印结果")
        return "结果"
    }
               

apply

  1. 定义: 执行代码块,返回调用的对象
    @kotlin.internal.InlineOnly
    public inline fun <T> T.apply(block: T.() -> Unit): T {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        block()
        return this
    }
               
  2. 案例
    fun main() {
        val list = mutableListOf("a","b")
        list.apply {
            add("c")
            add("d")
        }
        //[a, b, c, d]
        println(list)
    }
               

let

  1. 定义:将当前调用对象作为参数传入代码块,并返回代码块结果
    @kotlin.internal.InlineOnly
    public inline fun <T, R> T.let(block: (T) -> R): R {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        return block(this)
    }
               
  2. 案例
    fun main() {
        //输出:2
        2.let { println(it) }
        //输出:A
        "A".let { println(it) }
        /**
         * 输出
         *  - 打印结果
         *  - 结果
         */
        queryData().let { println(it) }
    }
    
    fun queryData(): String {
        println("打印结果")
        return "结果"
    }
               

also

  1. 定义:将当前调用对象作为参数传入代码块,并返回当前对象
    @kotlin.internal.InlineOnly
    @SinceKotlin("1.1")
    public inline fun <T> T.also(block: (T) -> Unit): T {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        block(this)
        return this
    }
    
               
  2. 案例
    //A
        //A
        var a = "A".also { println(it) }
        println(a)
               

With

  1. 定义:传入一个接收者对象,使用该对象去调用代码块
    @kotlin.internal.InlineOnly
    public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
        contract {
            callsInPlace(block, InvocationKind.EXACTLY_ONCE)
        }
        return receiver.block()
    }
               
  2. 案例
    val list1 = mutableListOf("a", "b")
        with(list1) {
            add("c")
            //输出[a, b, c]
            println("$this")
        }.let {
            //输出:kotlin.Unit
            println(it)
        }
               

继续阅读