Kotlin——函数和Lambda表达式

时间:2021-6-9 作者:qvyue

Kotlin对Java的纯粹面向对象进行了弥补,增加了函数式编程的支持。

函数入门

函数就是Kotlin程序的重要组成单位,一个Kotlin程序可以由多个函数组成

定义和调用函数

定义函数的语法格式:

fun 函数名(形参列表)[:返回值类型]{
  //0到多条可执行语句组成
}

Kotlin声明函数必须使用fun关键字

  • 函数名 函数名应该由一个或多个有意义的单词连缀而成,第一个单词首字母小写,后面每个单词首字母大写
  • 返回值类型
    返回值类型可以是Kotlin语言所允许的任何数据类型
  • 形参列表
    形参列表用于定义该函数可以接受的参数,形参列表由零组到多组“形参名:参数类型”组合而成,多数参数之间以,隔开,形参名和形参类型之间以:隔开。一旦在定义函数时指定了形参列表,那么在调用该函数时就必须传入对应的参数值
fun main(args: Array) {
//函数调用
println(max(234, 123))
}
fun max(x: Int, y: Int): Int {
    return when {
        x>y -> {
            x
        }
        x {
            y
        }
        else -> {
            x
        }
    }
}

函数返回值和Unit

如果希望声明一个函数没有返回值,则有如下两种声明方式。
* 省略:返回值类型
* 使用Unit 指定返回Unit 代表没有返回值 Unit就相当于Java的void

fun main(args: Array) {
    fool()
    unitReturn("wqnmd")
}
fun fool(){
    println("没有返回值函数")
}

fun unitReturn(name:String):Unit{
    println("这人名字叫${name}")
}

递归函数

在一个函数体内调用它自身,被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制

fun main(args: Array) {
    println(fn(10))
}

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

单表达式函数

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

fun main(args: Array) {
//    println(fn(10))
    println(area(2, 5))
}
fun area(x:Int,y:Int):Int=x*y

函数的形参

命名函数

Kotlin函数的参数名不是无意义的,Kotlin允许调用函数时通过名字来传入参数值——这些外部形参名与内部形参名保持一致

fun main(args: Array) {
    //传统调用方式
    println(girth(2.5, 3.0))
    //指定参数名
    println(girth(width = 3.0, height = 2.0))
    //混合使用
    println(girth(3.0, height = 4.0))
}

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

形参默认值

在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值,这样调用函数时就可以省略该形参
语法

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

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

fun main(args: Array) {
    //全部使用默认值
    sayHi()
    //message使用默认值
    sayHi("xiaoming")
    //两个参数都不使用默认值
    sayHi("xiaoming","china")
}

fun sayHi(name:String = "未知",message:String="消息"){
    println("${name}******${message}")
}

个数可变的形参

Kotlin允许定义个数可变的参数,从而允许为函数指定数量不确定的形参。如果在定义函数时,在形参的类型前添加vararg修饰,则表名该形参可以接受多个参数值,多个参数值被当成数组传入

fun main(args: Array) {
    variableParam(4,"a","b","c")
}

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

如果我们已有一个数组,程序希望将数组的多个元素传给个数可变的参数,则可以在传入的数组前添加”*”运算符

 var arr = arrayOf("a","b","c")
    variableParam(4,*arr)

函数重载

与Java类似,Kotlin允许定义多个同名函数,只要形参列表或返回值类型不同
如果程序包含了两个或两个以上函数名相同,但形参列表不同的函数,就称为函数的重载

fun test(){
    println("无参的test函数")
}

fun test(tInt:Int){
    println("重载的test${tInt}")
}

fun test(tInt:String):String{
    println("重载test函数,带返回值")
    return "123"
}

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

局部函数

Kotlin还支持在函数体内部定义函数,这种被放在函数体内定义的函数称为局部函数
在默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭函数内有效,封闭函数也可以返回局部函数

fun getFunMethod(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(type){
       "square" -> return square(nn)
        "cube" -> return cube(nn)
        else -> return factorial(nn)
    }
}

高阶函数

函数类型

Kotlin的每个函数都有特定的类型,函数类型由函数的形参列表、->、和返回值类型组成

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

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

使用函数类型

fun main(args: Array) {
    //定义函数类型变量
    var myfun:(Int,Int) -> Int
    //将函数类型变量赋值给pow
    myfun=::pow
    println(myfun(1, 2))
}

fun pow(base:Int,exponent:Int):Int{
    var result =1
    for (i in 1..exponent) {
        result*=exponent
    }
    return result
}

从上面代码可以看出,将pow()函数赋值给函数类型变量myfun——只要被赋值的函数类型与myfun的变量类型一致,程序就可以赋值成功
当直接访问一个函数的函数引用,而不是调用函数时,需要在函数名前添加两个冒号,而且不能再函数后面添加圆括号——一旦添加圆括号,就变成了调用函数,而不是访问函数引用。

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

有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定——这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,就需要在函数中定义函数类型的形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这些代码

fun main(args: Array) {
    //创建data数据
    var data = arrayOf(3, 4, 5, 6, 7, 8)
    //调用map,传入square函数
    println(map(data, ::square).contentToString())
    //调用map传入cube函数
    println(map(data, ::cube).contentToString())
}

fun map(data: Array, fn: (Int) -> Int): Array {
    //创建结果数组
    var result = Array(data.size, { 0 })
    //遍历data的每个元素,然后使用fn函数对data元素进行重新计算
    //然后将计算结果作为新数组的元素
    for (i in data.indices) {
        result[i] = fn(data[i])
    }
    return result
}

fun square(n: Int): Int {
    return n * n
}

fun cube(n: Int): Int {
    return n * n * n
}

使用函数类型作为返回值

fun main(args: Array) {
    //传入square
    var mathFunc = getFunMethod("square")
    println(mathFunc(5))
    //传入cube
    var cubeMath = getFunMethod("cube")
    println(cubeMath(5))
}

fun getFunMethod(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
    }

    when (type) {
        "square" -> return ::square
        "cube" -> return ::cube
        else -> return ::factorial
    }
}

局部函数与Lambda表达式

Lambda表达式是功能更灵活的代码块,它可以在程序中被传递和调用

  • 语法
{形参列表 ->
  //零条到多条可执行语句
}
  • Lambda特点
    • Lambda表达式总是被大括号括起来
    • 定义Lambda表达式不需要fun关键字
    • 形参列表在->之前声明,参数类型可以省略
    • 函数体(Lambda表达式执行体)放在->之后
    • 函数的最后一个表达式自动被作为Lambda表达式的返回值,不需要return关键字

调用Lambda表达式


fun main(args: Array) {
    //定义一个Lambda表达式
    var square = { n: Int ->
        n * n
    }
    println(square(5))
    //定义第二个Lambda表达式
    var result = { base: Int, express: Int ->
        var result = 1
        for (i in 1..express) {
            result*=base
        }
        result
    }(4,3)
    println(result)
}

利用上下文推断Lambda的形参类型

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

  //如果可以确定类型,Lambda可以省略形参类型
    var square:(Int) -> Int = {n->n*n}
    //不可以确定类型,省略形参类型,代码错误
    var a = {i->i-1}

省略形参名

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

fun omitParamType() {
    //如果只有一个形参 可以省略形参 用it代替
    var square:(Int) -> Int = {it*it}
}

调用Lambda表达式的约定

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

个数可变的参数和Lambda参数

如果调用函数时最后一个参数是Lambda表达式,则可将Lambda表达式放在圆括号外面,这样就无须使用命名参数了

匿名函数

Lambda表达式虽然简洁,但是Lambda表达式不能指定返回值类型

匿名函数的用法

 var test = fun(x: Int, y: Int): Int {
        return x + y
    }
    println(test(2, 4))

如果系统可以推出匿名函数的形参类型,那么匿名函数允许省略形参类型

var listFilter = listOf(1,2,3,4,5)
    listFilter.filter(fun(el):Boolean{
        return Math.abs(el) > 20
    })
    println(listFilter)

匿名函数和Lambda表达式的return

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

内联函数

使用内联函数

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

inline fun map(data: Array, fn: (Int) -> Int): Array {
    //创建结果数组
    var result = Array(data.size, { 0 })
    //遍历data的每个元素,然后使用fn函数对data元素进行重新计算
    //然后将计算结果作为新数组的元素
    for (i in data.indices) {
        result[i] = fn(data[i])
    }
    return result
}

内联函数在编译时,编译器实际上会将传入的lambda代码复制、粘贴到map()函数中
这样不存在函数调用,自然也就不需要额外产生函数对象,也不会有捕获,也不需要处理函数调用的压栈和出栈开销

内联函数的缺点,以及使用场景

内联函数的本质是将被调用的Lambda表达式或函数的代码复制、粘贴到原来的执行函数中,如果被调用的Lambda表达式或函数的代码量非常大,且该Lambda表达式或函数多次被调用——每调用一次,该Lambda表达式或函数的代码就会被复制一次,因此势必带来程序代码量的增加。

部分禁止内联

使用inline修饰函数后,所有传入该函数的Lambda表达式或函数都会被内联化,如果我们又希望该函数中某一个或某几个函数类型的形参不会被内联化,则可考虑使用noinline修饰
简单来说,noinline用于显式阻止某一个或某几个形参内联化

fun main(args: Array) {
    test({ it * it }, { "疯狂${it}讲义" })
}

inline fun test(fn1: (Int) -> Int, noinline fn2: (String) -> String) {
    println(fn1(20))
    println(fn2("hello"))
}

非局部返回

在默认情况下,在Lambda表达式中并不允许直接使用return。由于内联的Lambda表达式会被直接复制、粘贴到调用它的函数中,故此时在该Lambda表达式中可以使用return,该return就像直接写在调用函数中一样,因此该内联的Lambda表达式中的return可用于返回它所在的函数,这种返回被称为非局部返回
这个没搞懂,以后再补吧

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。