Kotlin学习笔记(九)函数,Lambda表达式

时间:2021-01-16 19:13:51

函数

Kotlin中,使用fun关键字定义一个函数,函数参数使用 Pascal 表示法定义,即 name: type。参数用逗号隔开。每个参数必须有显式类型。

fun foo(arg1:String,arg2:Boolean,arg3:Int):Int{
//do somethong
return 1
}

当函数只有单个表达式时,可以省略花括号,直接写在=之后,如果返回值类型可由编译器推断出来,也可以省略返回值类型:

fun foo(arg1:String,arg2:Boolean,arg3:Int)=1//省略花括号和返回值类型后的函数形式

具有块代码体的函数,必须显式指定返回值类型,除非返回值类型是Unit。编译器不会推断具有块代码体的函数的返回类型,因为这样的函数在代码体中可能有复杂的控制流,并且返回类型对于读者(又是甚至是编译器)是很不明显的。

fun foo(arg1:String,arg2:Boolean,arg3:Int):Int{
print("arg1="+arg1+"&arg2="+arg2+"arg3="+arg3)
return 0
}

调用函数使用传统的方法

var value=foo()

成员函数的调用要使用点表示法

R().foo()

函数还可以使用中缀表示法来调用,当满足一下条件时:

infix fun Int.add(x:Int):Int{
return this+x
}

fun printAdd(){
val value=2 add 3//中缀表示法调用add函数
print("the value is $value")
}

我们还可以在调用函数的时候使用命名的参数:

fun foo(arg1:String="Hello Kotlin",arg2:Boolean,arg3:Int){
print("arg1="+arg1+"&arg2="+arg2+"arg3="+arg3)
}

fun printFoo(){
foo(arg2 = true,arg3 = 7)//命名参数调用foo函数
}

函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量。

fun foo3(arg1: String,arg2: Boolean=false,arg3: Int=0){//默认参数要放到函数参数的最后面
print("arg1="+arg1+"&arg2="+arg2+"arg3="+arg3)
}

fun printFoo3(){
foo3("Hello Kotlin")//调用时,可以不传入默认参数的值,这时默认参数就会使用默认值
}

我们还可以为函数定义可变数量的参数,只要用vararg关键字修饰即可:

//可变数量的参数
fun foo4(vararg args:Int){
for (arg in args){
print(arg.toString()+",")
}
}

fun printFoo4(){
foo4(1,2,3,4,5)//1,2,3,4,5,
}

如果我们已经有一个数组并希望将其内容传给该函数,我们使用伸展(spread)操作符(在数组前面加 *):

//可变数量的参数
fun foo4(vararg args:Int){
for (arg in args){
print(arg.toString()+",")
}
}

fun printFoo4(){
val values= intArrayOf(1,2,3,4,5)
foo4(*values)//使用扩展运算符传一个数组给可变参数
}

高阶函数

高阶函数可以将一个函数作为参数或返回值:

fun add2(x:Int=0,y:Int=0):Int{
return x+y
}

fun operate(x:Int=0,y:Int=0,body:(Int,Int)->Int){//body是一个函数类型,传入两个Int类型参数,返回一个Int类型参数
print("this result is "+body(x,y))
}

fun getValue(){
operate(3,7,::add2)
}

Lambda表达式

以上的operate()方法,我们还有一个更简洁的调用方式,即传入一个lambda表达式:

fun getValue(){
operate(3,7,::add2)//函数参数传入一个函数
operate(3,7,{x,y->x+y})//函数参数传入一个lambda表达式
}

Lambda 表达式的完整语法形式,即函数类型的字面值如下:

val sum = { x: Int, y: Int -> x + y }

lambda 表达式总是被大括号括着, 完整语法形式的参数声明放在括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。

如果我们把所有可选标注都留下,看起来如下:

val sum: (Int, Int) -> Int = { x, y -> x + y }

当函数参数是最后函数的最后一个参数,并且你传入一个lambda表达式作为相应的参数,则可以在圆括号之外指定它:

fun getValue(){
operate(3,7,::add2)//函数参数传入一个函数
operate(3,7,{x,y->x+y})//函数参数传入一个lambda表达式
operate(3,7){//函数参数作为函数的最后一个参数,并且传入的是一个lambda表达式,可以在在圆括号外指定
x,y->x+y
}
}

如果lambda表达式只有一个参数,kotlin可以自己计算出签名,它允许我们不声明唯一的参数,并且隐含的为我们声明其名称为it:

fun upCase(str:String,body:(String)->String):String{//body是一个函数参数,传入一个String类型参数,返回一个String类型
return body(str)
}
fun transform(){
upCase("HelloKotlin"){//函数字面值只有一个参数,可以省略参数声明,其名称是it
it.toUpperCase()
}
}

如果lambda表达式是调用的唯一参数,则调用中的圆括号可以完全省略:

fun String.upper(body:(String)->String):String{
return body(this)
}

fun transform(){
"HelloKotlin".upper { it.toUpperCase() }//lambda是调用的唯一参数,则调用的圆括号可以省略
}

匿名函数

匿名函数与常规函数一样,只是省略了函数名称而已:

fun String.upper(body:(String)->String):String{
return body(this)
}

fun transform(){
"HelloKotlin".upper { it.toUpperCase() }//lambda表达式是调用的唯一参数,则调用的圆括号可以省略
"HelloKotlin".upper(fun(str:String):String{//将匿名函数作为一个函数参数传入
return str.toUpperCase()
})

}

Lambda表达式和匿名函数之间的另一个区别是非局部返回的行为。一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。

fun foo() {
ints.forEach {
if (it == 0) return//这个 return 表达式从最直接包围它的函数即 foo 中返回。
print(it)
}
}

值得注意的是:这种非局部的返回只支持传给内联函数的 lambda 表达式。 如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制 return。

fun transform():String{
"HelloKotlin".upper {
print(it.toUpperCase())
return@upper it.toUpperCase()//返回必须加标签限制
}
"HelloKotlin".upper(fun(str:String):String{
return str.toUpperCase()//从匿名函数返回
})
}

带接收者的函数字面值

Kotlin提供了指定的接收者调用函数字面值的功能。在函数字面值的函数体中,可以调用该接收者对象上的方法而无需任何额外的限定符。
这样的函数字面值的类型是一个带有接收者的函数类型:

sum : Int.(other: Int) -> Int

该函数字面值可以这样调用,就像它是接收者对象上的一个方法一样:

1.sum(2)

匿名函数语法允许你直接指定函数字面值的接收者类型。 如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用。

val sum = fun Int.(other: Int): Int = this + other