Kotlin——函数和Lambda表达式

函数是执行特定任务的一段代码。程序通过将一段代码定义成函数,并为改函数指定一个函数名。这样即可在需要的时候多次调用这段代码。

定义函数和调用函数

定义函数的语法格式如下

fun 函数名(形参列表)[:返回值类型]{
//可执行语句
}
  • 声明函数必须使用fun关键字
  • 函数名第一个单词首字母小写后面的每个单词首字母大写
  • 返回值类型可以是kotlin语言所允许的任何数据类型。如果声明了返回值类型,则函数体内应该有一条return语句,如果没有返回值类型,有两种声明方式:
    • 省略:返回值类型的部分
    • 使用:Unit指定返回Unit代表没有返回值。Kotlin的Unit就相当于Java的void
  • 形参列表:形参列表用于定义该函数可以接受的参数,形参列表由零组到多组“形参名:参数类型”组合而成,多组参数之间以英文逗号,隔开,形参名和形参类型之间以英文冒号:隔开。一旦指定了形参列表,在调用该函数时就必须传入对应的参数值

fun max(x: Int, y: Int): Int {
    val z = if (x > y) x else y
    return z
}

fun sayHi(name: String): String {
    return "${name},您好"
}

函数返回值和unit

如果希望明确指定函数没有返回值,有如下两种方式

  • 直接省略":返回值类型"的部分
  • 使用“:Unit”声明代表没有返回值
fun callMax():Unit {
    val a = 1
    val b = 2
    println("最大值为${max(a, b)}")
}

递归函数

在一个函数体内调用它自身,这种函数被称为递归函数
例如有一个数学题:f(0)=1,f(1)=4,f(n+2)=2f(n+1)+f(n)可以使用递归来解决

fun fn(n: Int): Int {
    if (n ==0) {
        return 1;
    }
    else if (n ==1) {
        return 4;
    }else{
        return 2* fn(n-1)+ fn(n-2)
    }
}

注意 递归一定要想已知方向进行,避免造成无穷递归

单表达式函数

在某些情况下,函数只是返回单个表达式,此时可以省略花括号并在等号后指定函数体

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

函数的形参

命名参数

Kotlin函数除第一个参数之外,其他所有形参部分都分配隐式的外部形参名——这些外部形参名与内部形参名保持一直

fun main(args: Array) {
    //传统调用函数的方式
    girth(2.0, 4.0)
    //使用形参名传入参数
    girth(width = 2.1, height = 1.1)
    //使用命名参数可指定位置
    girth(height = 1.1, width = 2.5)
    //可使用部分形参名
    girth(1.1, height = 5.0)
}


fun girth(width: Double, height: Double): Double {
    return 2*(width+height)
}

如果希望调用函数时混合使用命名参数和位置参数,那么命名参数必须位于位置参数之后

形参默认值

在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值

形参名:形参类型=默认值

形参的默认值紧跟在形参类型之后,中间以英文等号隔开

fun main(args: Array) {
    //全部使用默认参数
    sayHi()
    //使用一个默认参数
    sayHi("猪八戒")
    //全部不使用默认参数
    sayHi("沙和尚","西天取经")

    sayHi(message = "就是玩")
}

fun sayHi(name:String="孙悟空",message:String="啥也不是"){
    println("${name}${message}")
}

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

尾递归函数

Kotlin还支持一种尾递归函数的编码方式,当函数将调用自身作为它执行的最后一行代码,且递归调用后没有更多的代码时,可使用尾递归语法。

  • 尾递归不能在异常处理的try、catch、finally块中使用
  • 尾递归函数需要使用tailrec修饰
fun tailrecfun(n:Int):Int{
    //计算阶乘
    if (n==1) {
        return n
    }else {
        return n* tailrecfun(n-1)
    }
}
//尾递归写法
tailrec fun tailRec(n:Int):Int = if(n==1) n else n*tailRec(n-1)

个数可变形参

在定义函数时,在形参名称前添加vararg修饰,则表明该形参可以接受多个参数值,多个参数值被当成数组传入

fun multiParam(a:Int,vararg books:String){
    //books会被当作数组处理
    for (book in books) {
        println(book)
    }
    println(a)
}

kotlin允许个数可变的形参可以处于形参列表的任意位置(不要求是形参列表的最后一个参数),但是要求一个函数最多只能带一个个数可变的形参

如果我们已经有一个数组,希望将数组传给可变形参,则可以在传入的数组参数前添加"*"运算符

  var arr = arrayOf("疯狂java讲义", "glide从入门到精通", "c++从入门到放弃")
    multiParam(11, *arr)

函数重载

与Java类似,Kotlin允许定义多个同名函数,只要形参列表不同或返回值类型不同。

Kotlin的函数重载也只能通过形参列别区分,形参个数不同、形参类型不同都可以算重载。但仅有形参名不同、返回值类型不同、或修饰符不同,则不能算重载

局部函数

Kotlin支持在函数体内部i当以函数,称作局部函数
局部函数对外是隐藏的,只能在其封闭函数内有效,封闭函数也可以返回局部函数。

fun getMathFunc(type: String, nn: Int,mm:Int): Int {
    //定义局部函数
    fun add(a: Int, b: Int): Int {
        return a+b
    }

    fun minus(a: Int, b: Int): Int {
        return a - b
    }

    fun multi(a: Int, b: Int): Int {
        return a * b
    }

    fun divis(a: Int, b: Int): Int {
        return a / b
        
    }
    when (type) {
        "add" -> { return add(nn,mm)}
        "minus" -> { return minus(nn,mm)}
        "multi" -> { return multi(nn,mm)}
        "divis" -> { return divis(nn,mm)}
        else -> {
            return add(nn,mm)
        }
    }
}

高阶函数

Kotlin不是纯粹的面向对象语言,Kotlin的函数也是一等公民,因此函数本身也具有自己的类型。就是函数类型,函数类型就像前面介绍的数据类型一样,可以用于定义变量,也可用作函数的形参类型,还可作为函数的返回值类型

使用函数类型

函数类型由函数的形参列表、->和返回值类型组成,例如

func foo(a:Int,name:String) -> String{
}

该函数的形参列表、->和返回值类型为(Int,String)->String 这就说该函数的类型

func bar(width:Double,height:Double){}

该函数的形参列表、->和返回值类型为(Double,Double)->Unit或(Double,Double)这就是该函数的类型

func test(){}

该函数的形参列表、->和返回值类型为()->Unit或()

我们可以这样定义一个函数类型的变量

var myfun:(Int:Int)->Int

定义了函数类型的变量后,接下来就可以对函数变量赋值

当直接访问一个函数的引用,而不是调用函数时,需要在函数名前添加两个冒号::

fun main(args: Array) {
//    multiParam(12,"疯狂java讲义","glide从入门到精通","c++从入门到放弃")
val myfun:(Int,Int)->Int
    //将pow函数的引用赋值给函数类型myfun变量
    myfun = ::pow
    println(myfun(2, 3))
}
fun pow(base: Int, exponext: Int): Int {
    return base*exponext
}

使用函数类型作为形参类型

fun main(args: Array) {
    var paramArr = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)
    println(map(paramArr, ::addOne).contentToString())
}

/**
 * 将数组中的每个元素+1
 */
fun map(arrs: Array, fn: (Int) -> (Int)): Array {
    //初始化数组
    var result = Array(arrs.size, { 0 })
    //遍历arrs中的元素,对每个元素执行fn()运算
    for (i in arrs.indices) {
        result[i] = fn(arrs[i])
    }
    return result
}

fun addOne(args:Int):Int{
    return args+1
}

使用函数类型作为返回值类型

fun main(args: Array) {
    //获取add函数
    var add = getMathFunc("add")
    println(add(1, 1))
}

fun getMathFunc(type: String): (Int,Int)->Int {
    //定义局部函数
    fun add(a: Int, b: Int): Int {
        return a+b
    }

    fun minus(a: Int, b: Int): Int {
        return a - b
    }

    fun multi(a: Int, b: Int): Int {
        return a * b
    }

    fun divis(a: Int, b: Int): Int {
        return a / b

    }
    when (type) {
        "add" -> { return ::add}
        "minus" -> { return ::minus}
        "multi" -> { return ::multi}
        "divis" -> { return ::divis}
        else -> {
            return ::add
        }
    }
}

局部函数与Lambda表达式

Lambda表达式时现代编程语言中引入的一种语法,Lambda更加灵活

Lambda的语法

{(形参列表)->
//零到多条可执行语句
}

定义Lambda表达式有如下几点:

  • Lambda表达式总是被大括号括着
  • 定义Lambda表达式不需要fun关键字,无须指定函数名
  • 形参列表在->之前声明,参数类型可以省略
  • Lambda表达式的执行体放在->之后
  • 函数的最后一个表达式自动被作为Lambda表达式的返回值,无须使用return关键字
    上面的局部函数 我们可以改造为
fun main(args: Array) {
    var mathFunc = getMathFunc("divis")
    println(mathFunc(32, 4))

}

fun getMathFunc(type: String): (Int, Int) -> Int {

    when (type) {
        "add" -> {
            return { a: Int, b: Int -> a + b }
        }
        "minus" -> {
            return { c: Int, d: Int -> c - d }
        }
        "multi" -> {
            return { e: Int, f: Int -> e * f }
        }
        "divis" -> {
            return { h: Int, i: Int -> h / i }
        }
        else -> {
            return { a: Int, b: Int -> a + b }
        }
    }
}

调用Lambda表达式

Lambda表达式的本质是功能更灵活的代码块,因此完全可以将Lambda表达式赋值给变量或直接调用Lambda表达式

fun callLambdaExpression(){
    //定义lambda表达式,并赋值给square变量
    var square = {n:Int -> n*n}
    //调用Lambda表达式
    println(square(100))
    //定义第二个lambda表达式
    var result = {base:Int,exponent:Int ->
        base*exponent
    }(3,4)
    println(result)
}

上面程序中的第二个表达式没有赋值给任何变量,也没有将Lambda表达式传给任何函数或方法,因此程序只能在定义该表达式的同时调用它。程序在第二个Lambda表达式的后面使用圆括号执行调用,并传入相应参数

利用上下文推断类型

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

    //定义lambda表达式,并赋值给square变量 square指定类型
    var square:(Int)->Int = {n-> n*n}
    //调用Lambda表达式
    println(square(100))

省略形参名

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

    var square:(Int)->Int = {it*it}

上面it代表lambda表达式的形参,由于该lambda表达式只有一个形参 所以形参名可以省略 用it代替

调用Lambda表达式的约定

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

    var list = listOf("Java", "Kotlin", "Go")
    println(list.dropWhile(){ it.length > 3 })

个数可变的参数和Lambda参数

Kotlin约定:如果调用函数时最后一个参数是Lambda表达式,则可将Lambda表达式放在圆括号的外面,如果一个函数既包含个数可变的形参,也包含函数类型的形参,那么就应该将函数类型的形参放在最后

匿名函数

Lambda有个缺陷就是不能指定返回值类型。如果Kotlin无法推断出Lambda表达式的返回值类型,就需要显式指定返回值类型,而匿名函数即可代替Lambda表达式

匿名函数的用法

fun anonymousFunction(){
    //创建匿名函数
    var test = fun(x: Int, y: Int): Int {
        return x+y
    }
    println(test(2, 3))
}

匿名函数与普通函数基本相似。将普通函数的函数名去掉就变成了匿名函数

匿名函数和Lambda表达式的return

匿名函数的本质依然是函数,因此匿名函数的return则用于返回该函数本身,Lambda表达式的return用于返回所在的函数

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

Lambda表达式或者匿名函数(局部函数、对象表达式)可以访问或修改其所在上下文中的变量和常量。这个过程称之为捕获。

Lambda表达式或匿名函数都会持有一个其所捕获的变量的副本

内联函数

在调用Lambda表达式或函数的过程中,程序要将执行顺序转移到被调用表达式或函数所在的内存地址,当被调用表达式或函数执行完毕后,再返回到原函数执行的地方,从这里可以看出,函数调用会产生一定的时间和空间的开销。为了避免产生函数调用的过程,我们可以考虑直接把调用的表达式或函数代码”嵌入“到原来的执行流程中。这个通过内联函数来实现

内联函数的使用

使用内联函数,只要使用inline关键字修饰带函数形参的函数即可

//创建函数 对形参数组中的每个元素 执行fn函数
inline fun map(arr: Array, fn: (Int) -> Int):Array {
    var result = Array(arr.size,{0})
    for (i in arr.indices) {
        result[i] = fn(arr[i])
    }
    return result
}

使用inline编译时,会发现编译结果只产生一个class文件,不会生成其他额外的内部类的class文件,相当于编译器帮我们”复制、粘贴“了要调用的代码到执行的代码中

内联函数的缺点

内联函数的本质时将被调用的Lambda表达式或函数的代码复制、粘贴到原来的执行函数中,如果被调用的Lambda表达式或函数的代码量特别大,且该Lambda表达式或函数多次被调用,就会增加代码

部分禁止内联

使用inline修饰函数后,所有传入该函数的Lambda表达式或函数都会被内联化,那么可不可以部分内联呢?我们可以使用noinline来修饰
noinline用于显式阻止某一个或某几个形参内联化

非局部返回

在Lambda表达式中使用return返回的不是Lambda表达式而是返回该表达式所在的函数。由于内联的Lambda表达式会被拼接到调用它的函数中,此时在Lambda中使用return就直接写在了Lambda表达式的调用函数中一样,因此,该内联的Lambda表达式中的return可以返回所在的函数 这种返回就称为非局部返回

你可能感兴趣的:(Kotlin——函数和Lambda表达式)