Kotlin快速入门(五)——函数和Lambda表达式

函数和Lambda表达式

Kotlin对Java的存粹的面向对象进行了弥补,增加了函数式编程的支持,提高了编程的灵活性。对于Java程序员来讲,Kotlin的函数最需要花精力来掌握的内容。

1. 函数入门

  1. 定义和调用函数

    必须使用fun关键字声明。

    fun main(args: Array<String>) {
    
        println("3和5中较大的是${max(3, 5)}")
    }
    
    fun max(a: Int, b: Int): Int {
        return if (a > b) a else b
    }
    
  2. 函数返回值和Unit

    如果没有返回值可以省略,或者使用:Unit

    fun main(args: Array<String>) {
    
        sayHi("JamFF")
        showMsg("msg", 2)
    }
    
    fun sayHi(name: String) {
        println("你好,$name")
    }
    
    fun showMsg(msg: String, count: Int): Unit {
        for (i in 1..count) {
            println("$i: $msg")
        }
    }
    
  3. 递归函数

    已知数列:f(0) = 1, f(1) = 4, f(n+2) = 2 * f(n+1) + f(n),其中n是大于0的整数,求f(4)的值。

    fun main(args: Array<String>) {
    
        println("f(4) = ${f(4)}")
    }
    
    fun f(num: Int): Int {
        return if (num == 0) 1 else if (num == 1) 4 else 2 * f(num - 1) + f(num - 2)
    }
    
  4. 单表达式函数

    函数只是返回单个表达式,可以省略花括号并在等号后指定函数体即可。这种方式被称为单表达式函数。

    fun main(args: Array<String>) {
    
        println(area(2.0, 3.0))
    }
    
    fun area(x: Double, y: Double): Double = x * y
    

    编译器可以推断出函数返回值类型,还可以省略返回值类型。

    fun area(x: Double, y: Double) = x * y
    

2. 函数的形参

  1. 命名参数(具名参数)

    fun main(args: Array<String>) {
    
        println(area(width = 2.0, height = 3.0))
    
        println(area(height = 3.0, width = 2.0))
    
        println(area(2.0, height = 3.0))
    
        // 报错,命名参数必须在位置参数后面
        println(area(width = 2.0, 3.0))
        // 报错,位置参数是width,后面的命名参数还是width
        println(area(3.0, width = 2.0))
    }
    
    fun area(width: Double, height: Double): Double = width * height
    
  2. 形参默认值

    通过形参默认值,可以减少函数重载数量

    fun main(args: Array<String>) {
    
        showMsg()
        showMsg("Tony", "Hello Java")
        showMsg("Tony")
        showMsg(msg = "Hello Java")
    }
    
    fun showMsg(name: String = "JamFF", msg: String = "Hello Kotlin") {
        println("$name: $msg")
    }
    

    通常建议将带默认值的参数定义在形参列表的最后

    fun main(args: Array<String>) {
    
        printTriangle('*')
    }
    
    // 打印三角形
    fun printTriangle(char: Char, height: Int = 5) {
        for (i in 1..height) {
            for (j in 0 until height - 1) {
                print(" ")
            }
            for (j in 0 until 2 * i - 1) {
                print(char)
            }
            println()
        }
    }
    
  3. 尾递归函数

    当函数将调用自身作为它执行的最后一行代码,且递归调用后没有更多的代码时可以使用尾递归语法。
    另外,尾递归不能在异常处理的try、catch、finall块中使用。
    尾递归函数需要使用tailrec修饰。

    fun main(args: Array<String>) {
    
        println(fact(4))// 输出24
        println(facRec(4))// 输出24
    }
    
    // 普通递归,计算阶乘。这里的返回值不能省略,使用递归else返回值不明确
    fun fact(n: Int): Int = if (n == 1) 1 else n * fact(n - 1)
    
    // 尾递归优化,计算阶乘。这里的返回值不能省略,使用递归else返回值不明确
    tailrec fun facRec(n: Int, total: Int = 1): Int =
            if (n == 1) total else facRec(n - 1, total * n)
    

    与普通递归相比,编译器会进行优化,减少内存消耗

  4. 个数可变的形参(可变参数)

    在形参的类型前添加vararg修饰,表示该形参可以接受多个参数值,多个参数值被当作数组传入。

    fun main(args: Array<String>) {
    
        test(5, "JamFF", "Jason")
    }
    
    fun test(a: Int, vararg names: String) {
        for (name in names) {
            println(name)
        }
        println(a)
    }
    

    可变参数可以处于参数列表的任意位置(不要求是最后一个参数),但一个函数最多只能带一个可变参数,并且如果给可变参数后面的参数传参,必须使用命名参数

    fun main(args: Array<String>) {
    
        test("JamFF", "Jason", num = 5)
    }
    
    fun test(vararg names: String, num: Int) {
        for (name in names) {
            println(name)
        }
        println(num)
    }
    

    如果已经有一个数组,希望把数组传入可变参数,可以在数组参数前添加*运算符。

    fun main(args: Array<String>) {
        val arr = arrayOf("JamFF", "Jason")
        test(*arr, num = 5)
    }
    

3. 函数重载

与Java一致。
如果被重载的函数包含可变参数,Kotlin会尽量执行最精确的匹配。

fun main(args: Array<String>) {
        test()// 可变参数
        test("JamFF", "Jason")// 可变参数
        test("JamFF")// 一个参数
    }
    
    fun test(msg: String) {
        println("只含有一个字符串的test函数 $msg")
    }
    
    fun test(vararg names: String) {
        println("可变参数 ${names.contentToString()}")
    }

大部分情况下不推荐重载可变参数的函数,没有意义并且容易导致错误。

4. 局部函数

Kotlin支持在函数体内部定义函数,这种函数被称为局部函数。
默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭(enclosing)函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域中使用局部函数。

fun main(args: Array<String>) {

    println(getMathFunc("square", 3))
    println(getMathFunc("cube", 3))
    println(getMathFunc("factorial", 3))
}

fun getMathFunc(type: String, nn: Int): Int {

    // 平方的局部函数
    fun square(n: Int): Int {
        return n * n
    }

    // 立方的局部函数
    fun cube(n: Int): Int {
        return n * n * n
    }

    // 阶乘的局部函数
    fun factorial(n: Int): Int {
        var result = 1
        for (index in 2..n) {
            result *= index
        }
        return result
    }

    // 使用when表达式,简化多个return
    return when (type) {
        "square" -> square(nn)
        "cube" -> cube(nn)
        else -> factorial(nn)
    }
}

如果程序使用变量保存了封闭函数的返回值,那么这个局部函数的作用域就会被扩大,和全局函数是一样的。

5. 高阶函数

Kotlin的函数是一等公民,也就是说可以想变量一样使用。高阶函数就是,以另一个函数作为参数或返回值的函数。

  1. 使用函数类型

    定义函数类型的变量

    fun main(args: Array<String>) {
    
        // 定义一个变量,类型为(Int, Int) -> Int
        val myFun: (Int, Int) -> Int
        // 定义一个变量,类型为(String) -> Unit,返回值不能省略
        val test: (String) -> Unit
    }
    

    赋值使用

    fun main(args: Array<String>) {
    
        // 定义一个变量,类型为(Int, Int) -> Int
        var myFun: (Int, Int) -> Int
        // 定义一个变量,类型为(String) -> Unit,返回值不能省略
        val test: (String) -> Unit
    
        test = ::show// 在函数前面使用::表示函数的引用
        test("JamFF")
    
        myFun = ::pow
        println(myFun(2, 4))// 计算2的4次方,输出16
    
        myFun = ::area
        println(myFun(2, 4))// 计算面积,输出8
    }
    
    fun show(msg: String) {
        println(msg)
    }
    
    // 计算乘方
    fun pow(base: Int, exponent: Int): Int {
        var result = 1
        for (i in 1..exponent) {
            result *= base
        }
        return result
    }
    
    // 计算面积
    fun area(width: Int, height: Int): Int {
        return width * height
    }
    
  2. 使用函数类型作为形参类型

    fun main(args: Array<String>) {
    
        val data = arrayOf(1, 2, 3, 4, 5)
        println("原数据${data.contentToString()}")
    
        println("计算元素平方${map(data, ::square).contentToString()}")
    
        println("计算元素立方${map(data, ::cube).contentToString()}")
    
        println("计算元素阶乘${map(data, ::factorial).contentToString()}")
    }
    
    // 平方
    fun square(n: Int): Int {
        return n * n
    }
    
    // 立方
    fun cube(n: Int): Int {
        return n * n * n
    }
    
    // 阶乘
    fun factorial(n: Int): Int {
        var result = 1
        for (index in 2..n) {
            result *= index
        }
        return result
    }
    
    // fn是函数类型的形参
    fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
    
        val result = Array(data.size) { 0 }
    
        for (i in data.indices) {
            result[i] = fn(data[i])
        }
    
        return result
    }
    
  3. 使用函数类型作为返回值类型

    fun main(args: Array<String>) {
    
        var mathFunc = getMathFunc("cube")
        println(mathFunc(5))// 输出125
    
        mathFunc = getMathFunc("square")
        println(mathFunc(5))// 输出25
    
        mathFunc = getMathFunc("other")
        println(mathFunc(5))// 输出120
    }
    
    // 返回值类型是函数类型
    fun getMathFunc(type: String): (Int) -> Int {
    
        // 平方
        fun square(n: Int): Int {
            return n * n
        }
    
        // 立方
        fun cube(n: Int): Int {
            return n * n * n
        }
    
        // 阶乘
        fun factorial(n: Int): Int {
            var result = 1
            for (index in 2..n) {
                result *= index
            }
            return result
        }
    
        // 返回局部函数
        return when (type) {
            "square" -> ::square
            "cube" -> ::cube
            else -> ::factorial
        }
    }
    

6. 局部函数与Lambda表达式

如果说函数是命名的、方便复用的代码块,那么Lambda表达式则是功能更灵活的代码块,它可以在程序中被传递和调用。

  1. 使用Lambda表达式代替局部函数

    使用Lambda表达式简化上面的代码。

    fun main(args: Array<String>) {
    
        var mathFunc = getMathFunc("cube")
        println(mathFunc(5))// 输出125
    
        mathFunc = getMathFunc("square")
        println(mathFunc(5))// 输出25
    
        mathFunc = getMathFunc("other")
        println(mathFunc(5))// 输出120
    }
    
    // 返回值类型是函数类型
    fun getMathFunc(type: String): (Int) -> Int {
    
        // 返回局部函数
        return when (type) {
            "square" -> { n: Int ->
                n * n
            }
            "cube" -> { n: Int ->
                n * n * n
            }
            else -> { n: Int ->
                var result = 1
                for (index in 2..n) {
                    result *= index
                }
                result
            }
        }
    }
    
  2. Lambda表达式的脱离

    作为函数参数传入的Lambda表达式可以脱离函数独立使用。

    fun main(args: Array<String>) {
    
        collectFn { a: Int -> a * a }
        // 如果只有一个形参,Kotlin可以省略形参名,如果省略的话,->也不需要了,用it代替形参。
        collectFn { it * it }// 上面表达式的简写,在下面Lambda表达式会详细说到
        collectFn { it * it * it }
    
        for (i in lambdaList.indices) {
            println(lambdaList[i](i + 2))
        }
    }
    
    // 定义一个List类型的变量
    val lambdaList = ArrayList<(Int) -> Int>()
    
    // 返回值类型是函数类型
    fun collectFn(fn: (Int) -> Int) {
    
        // 将传入的fn(函数或Lambda表达式)添加到集合
        lambdaList.add(fn)
    }
    

上面程序把Lambda表达式作为参数传给collectFn()函数后,这些Lambda表达式可以脱离collectFn()函数使用。

7. Lambda表达式

Lambda表达式的标准语法如下:

{ (形参列表) ->
    // 零条到多条可执行语句
}
  • Lambda表达式总是被大括号括着。
  • 定义Lambda表达式不需要fun关键字,无须制定函数名。
  • 形参列表(如果有的话)在->之前声明,参数类型可以省略。
  • 函数体(Lambda表达式执行体)放在->之后。
  • 函数的最后一个表达式自动被作为Lambda表达式的返回值,无须使用return关键字。
  1. 调用Lambda表达式

    可以将Lambda表达式赋值给变量或直接调用Lambda表达式

    fun main(args: Array<String>) {
    
        // 将Lambda表达式赋值给变量
        val square = { n: Int ->
            n * n
        }
    
        println(square(5))// 输出25
        
        // 在Lambda表达式后面添加圆括号,直接调用
        val result = { base: Int, exponent: Int ->
            var result = 1
            for (i in 1..exponent) {
                result *= base
            }
            result
        }(4, 3)
    
        println(result)// 输出64
    }
    
  2. 利用上下文推断类型

    完整的Lambda表达式需要定义形参类型,但是如果Kotlin可以根据Lambda表达式上下文推断出形参类型,那么就可以省略形参类型。

    fun main(args: Array<String>) {
    
        // Lambda表达式被赋值为(Int) -> Int类型的变量,可以推断形参类型
        val square: (Int) -> Int = { n -> n * n }
    
        println(square(5))// 输出25
    
        // 直接传参(4, 3),不能推断形参类型,所以需要显式声明形参类型
        val result = { base: Int, exponent: Int ->
            var result = 1
            for (i in 1..exponent) {
                result *= base
            }
            result
        }(4, 3)
    
        println(result)// 输出64
    
        val list = listOf("Java", "Kotlin", "Go")
    
        // 因为dropWhile方法的形参是(T) -> Boolean类型,所以可以推断形参类就是集合元素类型
        // dropWhile返回一个新List,返回从第一项起,去掉满足条件的元素(lambda返回true),直到不满足条件的一项为止
        val rt = list.dropWhile({ e -> e.length > 3 })
    
        // Lambda可以简化,在下面Lambda表达式会详细说
        // val rt = list.dropWhile { it.length > 3 }
    
        println(rt)// 输出[Go]
    }
    
  3. 省略形参名

    Lambda表达式不仅可以省略形参类型,而且如果只有一个形参,那么Kotlin允许省略Lambda表达式的形参名,如果省略了,那么->也不需要了,Lambda表达式可通过it来代表形参。

    fun main(args: Array<String>) {
    
        // 用it代表形参
        val square: (Int) -> Int = { it * it }
    
        println(square(5))// 输出25
    
        val list = listOf("Java", "Kotlin", "Go")
    
        // 用it代表形参
        val rt = list.dropWhile({ it.length > 3 })
    
        println(rt)// 输出[Go]
    }
    
  4. 调用Lambda表达式的约定

    如果函数的最后一个参数是函数类型,并且你打算传入一个Lambda表达式作为相应的参数,那么就允许在圆括号之外指定Lambda表达式。

    fun main(args: Array<String>) {
    
        val list = listOf("Java", "Kotlin", "Go")
        // 最后一个参数是Lambda表达式,可以将表达式写在圆括号外面
        val rt = list.dropWhile() { it.length > 3 }
        println(rt)// 输出[Go]
    
        val map = mutableMapOf("Android" to 666)
        // 最后一个参数是Lambda表达式,可以将表达式写在圆括号外面
        list.associateTo(map) { it to it.length }
        println(map)// 输出{Android=666, Java=4, Kotlin=6, Go=2}
    
        // 最后一个参数是Lambda表达式,可以将表达式写在圆括号外面
        val rtx = list.reduce() { acc, s -> acc + s }
        println(rtx)// 输出JavaKotlinGo
    }
    

    如果Lambda表达式是函数调用的唯一参数,调用方法时的圆括号可以省略。

    val rt = list.dropWhile { it.length > 3 }
    val rtx = list.reduce { acc, s -> acc + s }
    

    通常建议将函数类型的形参放在参数列表的最后,方便传入Lambda表达式作为参数。

  5. 个数可变的参数和Lambda参数

    虽然Kotlin允许将可变参数定义在形参列表的任意位置,但如果不将可变参数定义在最后,那么就只能用命名参数的形式给可变参数之后的其他形参传值。

    但上面又建议将函数类型的参数放在形参列表的最后。如果一个函数既包含个数可变的形参,也包含函数类型的形参,这就产生了冲突。Kotlin约定:如果调用函数时最后一个参数是Lambda表达式,则可将Lambda表达式放在圆括号外面,这样就无需使用命名参数了。

    因此答案是:将函数类型的形参放在最后

    fun <T> test(vararg names: String, transform: (String) -> T): List<T> {
        val mutableList: MutableList<T> = mutableListOf()
        for (name in names) {
            mutableList.add(transform(name))
        }
        return mutableList
    }
    
    fun main(args: Array<String>) {
    
        val list1 = test("Java", "Kotlin", "Go") { it.length }
        println(list1)// 输出[4, 6, 2]
        
        val list2 = test("Java", "Kotlin", "Go") { "$it${it.length}个字" }
        println(list2)// [Java4个字, Kotlin6个字, Go2个字]
    }
    

8. 匿名函数

Lambda表达式虽然简介、方便但是不能指定返回值类型。大部分时候,Kotlin可以推断出Lambda表达式的返回值类型。但在一些特殊的场景下无法推断,就需要显式指定返回值类型,而匿名函数即可代替Lambda表达式

  1. 匿名函数的用法

    匿名函数与普通函数类似,只要将普通函数去掉函数名就成了匿名函数。

    fun main(args: Array<String>) {
    
        // Lambda表达式定义变量
        val test1 = { x: Int, y: Int -> x + y }
        // 匿名函数定义变量
        val test2 = fun(x: Int, y: Int): Int {
            return x + y
        }
    
        println(test1(2, 4))// 输出6
        println(test2(2, 4))// 输出6
    }
    

    与普通函数不同的是,如果可以推断出匿名函数的形参类型,那么匿名函数允许省略形参类型。

    fun main(args: Array<String>) {
    
        val filteredList = listOf(3, 5, 20, 100, -25).filter(
                // filter()方法需要传入一个(Int) -> Boolean类型的参数
                // 当传入匿名函数时,可以推断出参数类型必须是(Int) -> Boolean,可以省略形参类型
                fun(el): Boolean {
                    return Math.abs(el) > 20
                }
        )
        println(filteredList)// 输出[100, -25]
    
        // filter()传入Lambda表达式
        val filteredList2 = listOf(3, 5, 20, 100, -25).filter { Math.abs(it) > 20 }
        println(filteredList2)// 输出[100, -25]
    }
    

    匿名函数的返回值类型的声明规则与普通函数相同。如果使用单表达式作为函数体,则无须指定返回值类型,系统可自动推断。

    fun main(args: Array<String>) {
    
        // 单表达式作为函数体,省略返回值类型
        val test = fun(x: Int, y: Int) = x + y
    
        val filteredList = listOf(3, 5, 20, 100, -25).filter(
                // 单表达式作为函数体,省略返回值类型
                fun(el) = Math.abs(el) > 20
        )
        println(test(2, 4))// 输出6
        println(filteredList)// 输出[100, -25]
    }
    
  2. 匿名函数和Lambda表达式的return

    匿名函数的本质依然是函数,因此匿名函数中的return用于返回函数本身;而lambda表达式的return用于返回它所在的函数。

    fun main(args: Array<String>) {
    
        val list = listOf(3, 5, 30, -25, 14)
        list.forEach(fun(n) {
            println(n)// 全部输出
            return
        })
    
        list.forEach {
            println(it)// 只输出3
            return
        }
    }
    

    如果一定要在Lambda中使用return,返回该函数本身,可以使用限定返回的语法。

    list.forEach {
        println(it)// 只输出3
        // 使用限定返回,此时return只是返回给forEach方法的Lambda表达式
        return@forEach
    }
    

9. 捕获上下文中的变量和常量

Lambda表达式或匿名函数(以及局部函数、对象表达式)可以访问或修改其所在上下文(俗称“闭包”)中的变量和常量,这个过程被称为捕获。即使定义这些变量和常量的作用域已经不存在,Lambda表达式或匿名函数也依然可以访问或修改它们。

例如下面先定义一个函数,然后在该函数内定义局部函数,此时局部函数就可以访问或修改其所在上下文(函数)中的变量。

// 定义一个函数,返回值类型是 () -> List<String>
fun makeList(ele: String): () -> List<String> {
    // 创建一个不包含任何元素的List
    val list: MutableList<String> = mutableListOf()

    // 局部函数,没有定义任何变量,却可以访问list和ele,因为捕获了其所在函数的变量
    fun addElement(): List<String> {
        list.add(ele)
        return list
    }
    return ::addElement
}

fun main(args: Array<String>) {

    println("-----add1 返回的List-----")
    val add1 = makeList("Java")
    println(add1())
    println(add1())

    println("-----add2 返回的List-----")
    val add2 = makeList("Kotlin")
    println(add2())
    println(add2())
}

运行结果

-----add1 返回的List-----
[Java]
[Java, Java]
-----add2 返回的List-----
[Kotlin]
[Kotlin, Kotlin]

10. 内联函数

先简单介绍一下高阶函数(为函数传入函数或Lambda表达式作为函数)的调用过程。调用Lambda表达式或函数的过程是:程序要将执行顺序转移到被调用表达式或函数所在的内存地址,当被调用表达式或函数执行完后,再返回到原函数执行的地方。

在上面这个转移过程中,系统要处理如下事情。

  • 为被调用的表达式或函数创建一个对象。
  • 为被调用的表达式或函数所捕捉的变量创建一个副本。
  • 在跳转到被调用的表达式或函数所在的地址之前,要先保护现场并记录执行地址;从被调用的表达式或函数地址返回时,要先恢复现场,并按原来保存的地址继续执行。也就是通常说的压栈和弹栈。

不难看出,函数调用会产生一定的时间和空间开销,如果被调用的表达式或函数的代码量本身不大,而且经常被调用,那么这个时间和空间开销的损耗就很不划算。

为了避免产生函数调用的过程,可以考虑直接把被调用的表达式或函数的代码“嵌入”原来的执行流中——简单来说,就是编译器负责“复制、粘贴”:复制被调用的表达式或函数的代码,然后粘贴到原来的执行代码中。为了让编译器帮我们干这个复制、粘贴的话,可通过内联函数来实现。

  1. 内联函数的使用

    只要使用inline关键字修饰带函数形参的函数即可。下面示范来内联函数和非内联函数的区别。

    inline fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
    
        val result = Array(data.size) { 0 }
    
        for (i in data.indices) {
            result[i] = fn(data[i])
        }
        return result
    }
    
    fun main(args: Array<String>) {
    
        val arr = arrayOf(20, 4, 40, 100, 30)
        val mappedResult = map(arr) { it + 3 }
        println(mappedResult.contentToString())
    }
    

    使用inline内联函数,编译器实际上会将Lambda表达式的代码复制、粘贴到map()函数中。也就是说,程序调用的map函数编译后实际上变成了如下形式:

    fun map(data: Array<Int>): Array<Int> {
    
        val result = Array(data.size) { 0 }
    
        for (i in data.indices) {
            result[i] = data[i] + 3
        }
        return result
    }
    

需要注意的是,内联函数并不总是能带来好处,因为内联函数的本质是将被调用的Lambada表达式或函数的代码复制、粘贴到原执行函数中。当Lambada表达式或函数包含大量的执行代码,不应使用内联函数;如果Lambada表达式或函数只包含非常简单的执行代码(尤其是单表达式),那么就应该使用内联函数

  1. 部分禁止内联

    使用inline修饰后,所有传入函数的Lambda表达式或函数都会被内联化,如果希望该函数中的某个或某几个函数类型的形参不被内联化,可以使用noinline修饰。

    inline fun test(fn1: (Int) -> Int, noinline fn2: (String) -> String) {
    
        println(fn1(8))
        println(fn2("Kotlin"))
    }
    
    fun main(args: Array<String>) {
    
        test({ it * it }, { it })
    }
    

    上面程序虽然使用inline修饰了test()函数,但是fn2形参使用了noinline修饰,它不会被内联化。

  2. 非局部返回

    前面提到在Lambda表达式中使用return不是用于返回该表达式,而是返回该表达式所在的函数。但要记住:默认情况下,在Lambda表达式中并不允许直接使用return。这是因为如果是非内联的Lambda表达式会额外生成一个函数对象,因此这种表达式中的return不可能用于返回它所在的函数。

    由于内联的Lambda表达式会被直接复制、粘贴到调用它的函数中,故此在该Lambda表达式中可使用return,该return就像直接写在Lambda表达式的调用函数中一样。因此,该内联的Lambda表达式中的return可用于返回它所在的函数,这种返回被称作非局部返回

    inline fun each(data: Array<Int>, fn: (Int) -> Unit) {
    
        for (el in data) {
            fn(el)
        }
    }
    
    fun main(args: Array<String>) {
    
        val arr = arrayOf(20, 4, 40, 100, 30)
        each(arr) {
            if (it == 4) {
                return@each// 返回each函数
            }
            if (it == 100) {
                //return// 如果each没有inline修饰,此处编译异常
                return
                // 如果each有inline修饰,return返回main函数
            }
    
            println(it)
        }
    }
    

    如果删除上面each()函数的inline修饰符,那么下面的Lambda表达式中的return将会提示编译错误:'return' is not allowed here,这意味着在非内联的Lambda表达式中不能使用return

    另外,有些内联函数不是从函数体中调用Lambda表达式的,而是从其他的执行上下文(如局部对象或局部函数)中来获取Lambda表达式的。在这种情况下,非局部返回的控制流也不与许出现在Lambda表达式中。此时应该使用crossinline来修饰该参数。

    inline fun f(crossinline body: () -> Unit) {
    
        val f1 = object : Runnable {
            override fun run() {
                body()
            }
        }
    
        // Lambda简写,和上面f1一样
        val f2 = Runnable { body() }
    }
    

重点

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

推荐阅读更多精彩内容