1、Koltin对Java的纯粹面向对象进行了弥补,增加了函数式编程的支持。即Kotlin融合了面向过程和面向对象语言的特征,因此Kotlin完全支持定义函数和调用函数。
Kotlin的函数比C语言的函数更强大,Kotlin支持局部函数(实际上局部函数是Lambda表达式的基础)。
==> 函数是Kotlin中非常重要的一个知识点!!!
2、fun 函数名(形参列表)[:返回值类型]{
//零到多条可执行的语句
}
形参列表由零到多组"形参名:参数类型"组成;
如果函数没有返回值,则可以省略:返回值类型或使用:Unit;
3、当程序调用一个函数时,既可以把调用函数的返回值赋值给变量,也可以将函数的返回值传给另一个函数,作为另一个函数的参数。
4、在某些情况下,函数只是返回单个表达式,此时可以省略花括号并在等号后指定函数体即可。这种方式被称为单表达式函数。
fun max(x:Int,y:Int):Int = if(x>y) x else y
对于单表达式函数而言,编译器可以推断出函数的返回值类型,因此Kotlin允许省略声明函数的返回值类型。
5、函数的形参
-
命名参数
Kotlin允许调用函数通过名字来传入参数,因此Kotlin函数的参数名应该具有更好的语义 - 程序可以立刻明确传入函数的每个参数的含义。
fun girth(width:Double,height:Double):Double{
。。。。。
}
调用:girth(1.1,1.2)或girth(width = 1.1,height = 1.2)或girth(height = 1.2,width = 1.1)或girth(1.1,height = 1.2)
需要说明的是,如果希望调用函数时,混合使用命名参数和位置参数,那么命名参数必须位于位置参数之后。换句话说就是命名参数之后只能是命名参数。比如:
girth(width = 1.1, 1.2)//位置参数必须放在命名参数之前,因此这行代码会报错
-
形参默认值【可以减少函数重载的数量】
语法:
形参名:形参类型 = 默认值
示例:
fun sanHi(name:String = "孙悟空",message:String="哈哈哈"){
println(name + message)
}
调用函数,如果只传入一个位置参数,由于该参数位于第一位,系统会默认将该参数值传给name参数。
Kotlin建议将带默认值的参数定义在形参列表的最后。
-
尾递归函数[tailrec修饰函数]
当函数将调用自身作为它执行的最后一行代码,且递归调用后,没有更多的代码时,可使用递归语法。
比如:
fun fact(n:Int):Int{
if(n==1)
return 1
else
return n*fact(n-1)
}
相当于
tailrec fun fact(n:Int,total:Int=1):Int = if (n==1) total else fact(n-1,total*n)
与普通的递归相比,编译器会对尾递归进行修改,将其优化为一个快速而高效的基于循环的版本,这样就可以减少可能对内存的消耗。
-
个数可变的形参[vararg修饰]
fun test(a:Int,vararg books:String){}
参数个数可变的形参本质是一个数组参数;
kotlin要求一个函数最多只能带一个个数可变的形参;
如果已有一个数组,希望将数组传给个数可变的形参,则可以在传入的数组参数前添加*运算符:
var book = arrayOf("java","kotlin","android")
test(3,*book)
-
函数重载:kotlin函数重载只能通过形参列表进行区分,形参的个数或类型不同都可以算函数重载。
6、局部函数
放在函数体内部的函数,称为局部函数。局部函数对外部是隐藏的,局部函数只能在其封闭函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域使用局部函数。
fun outterFun(type:String,nn:Int):Int{
fun square(n:Int):Int{
return n*n
}
fun cube(n:Int):Int{
return n*n*n
}
when(type){
"square"->return square(nn)
"cube" -> return cube(njn)
}}
如上程序所示,封闭函数并没有将局部函数返回,那么局部函数只能在封闭函数内部调用。
如果封闭函数将局部函数返回,且程序使用变量保存了封闭函数的返回值,则这些局部函数的作用域将扩大,因此程序完全可以自由地调用它们,就像它们是全局函数一样。
7、高阶函数
Kotlin不是纯粹的面向对象的语言,函数也是一等公民,因此函数本身也具有自己的类型。函数类型就像前面介绍的数据类型一样,既可以用于定义变量,也可以用于函数的形参类型,还可以作为函数的返回值类型。
-
函数类型
(形参列表)-> 返回值类型
例如:
fun test(m:Int,n:String):String{}
其函数类型就是(Int,String)->String
示例:定义变量
var fun:(Int,Int)->Int
fun pow(base:Int,exponent:Int):Int{
。。。}
fun = ::pow//当直接访问函数的引用,而不是调用函数的时候,需要在函数名前加冒号,而不能在函数名后加括号,一旦添加了括号,就变成函数调用了。
示例:作为形参类型
fun map(data:Array<Int>,fn:(Int,Int)->Int):Array<Int>{
.....
}
如果在定义函数的时候,该函数的大部分代码的逻辑是确定的,但是某些逻辑的处理是不确定的,这意味着某些代码是需要动态改变的,如果希望调用函数时能动态的传入这些代码,就需要在函数中定义函数类型的形参。如上所示。
map(arr,::square)
示例:作为返回值
fun getMathFun(type:String):(Int)->Int{
fun square(n:Int):Int{
return n*n
}
fun cube(n:Int):Int{
return n*n*n
}
when(type){
"square"->return ::square
"cube" -> return ::cube
}}
调用:var mathSquare = getMathFun("square")
8、Lambda表达式(本质是功能更加灵活的代码块)
如果说函数是命名的、方便复用的代码块,那么Lambda表达式则是功能更加灵活的代码块,它可以在程序中被传递和调用。
由上一节:
when(type){
"square"->return ::square
"cube" -> return ::cube
}
可以看到,作为返回值,局部函数的函数名一旦离开封闭函数就失去了意义。既然局部函数的函数名没有太大的意义,就可以考虑使用Lambda表达式来简化局部函数的写法。
when(type){
"square"->return {n:Int->n*n}
"cube" -> return {n:Int->n*n*n}
}
语法:
{形参列表->
//零到多条可执行语句
}
与局部函数的区别:
-
Lambda表达式总是被大括号括着
-
不需要fun关键字,无需指定函数名
-
形参列表(如果有的话)在->之前声明,参数类型可以省略
-
函数体(Lambda表达式执行体)放在->之后
-
函数的最后一个表达式自动被作为Lambda表达式的返回值,无需使用return关键字
8.1、Lambda表达式可以赋值给变量或者直接调用
var square = {n:Int->n*n}
square(2)
或
{n:Int->n*n}(2)//自调用
8.2、Lambda表达式的类型推断
完整的Lambda表达式需要定义形参类型,但是如果Kotlin可以根据Lambda表达式的上下文推断出形参类型,那么Lambda表达式就可以省略形参类型。(其实和普通变量的的类型推断没什么区别)
比如:
var square:(Int)->Int={n->n*n}//可推断,因此可省略形参类型
var square = {n->n*n}//错误,因为不可推断,所以必须加上形参类型,即{n:Int->n*n}
8.3、省略形参名
如果只有一个形参,Kotlin允许省略Lambda表达式的形参名。如果Lambda表达式省略了形参名,此时->也不需要了,Lambda表达式可通过it来代表形参。
比如:
var square:(Int)->Int={it*it}
8.4、调用Lambda表达式的约定
如果函数的最后一个形参是函数类型,而且你打算传入Lambda表达式作为相应的参数,那么就允许在括号之外指定Lambda表达式。
示例:
var rt = list.dropWhile(){it.length>3}
当然,如果Lambda表达式是函数调用的唯一参数,那么调用放大的圆括号也是可以省略的呀
var rt = list.dropWhile{it.length>3}
==>建议将函数类型的形参放在参数列表的最后,这样方便以后传入Lambda表达式
9、匿名函数
Lambda表达式有一个缺陷就是不能指定返回值的类型。由于Kotlin可以推断出Lambda表达式的返回值类型,因此即使不为Lambda表达式指定返回值类型也是没问题的。但在一些特殊的场景下,Kotlin无法推断出Lambda表达式的返回值类型,此时就需要显式指定返回值类型,或者匿名函数即可替代Lambda表达式。
var test = fun(x:Int,y:Int):Int{
}
9.1、匿名函数与类型推断
a、如果系统可以推断匿名函数的形参类型,那么匿名函数允许省略形参类型;
listOf(3,5,6,7).filter(
fun(e1):Boolean{....}
)
filter 方法的类型是:(Int)->Boolean,因此系统可推断该参数类型必是(Int)->Boolean,因此允许省略形参类型
b、返回值类型的声明规则:
如果使用普通代码块作为函数体,则必须显式的指定返回值类型,否则会被认为没有返回值;
如果使用单表达式作为函数体,则无需指定返回值类型,系统可以自动的推断。
var wawa = fun(x:Int,y:Int) = x*y
9.2、匿名函数和Lambda表达式的return
匿名函数的return作用于函数本身;
Lambda表达式的return作用域所在的函数(一般很少使用);
比如:
list.forEach(fun(n){
。。
return
})
list.forEach({n:Int->
。。。
return
})
如果上面两个调用均在main函数中,则第二种方式的调用只能遍历一个集合元素。
10、捕获上下文中的变量和常量(内部函数可访问外部函数定义的变量和常量)
Lambda表达式或匿名函数(以及局部函数、对象表达式)可以访问或修改(俗称“闭包”)其所在的上下文中的变量和常量。
即使定义这些变量和常量的作用域已经不存在了,Lambda表达式或匿名函数依然可以访问和修改它们。Why?
=》Lambda表达式或匿名函数都会持有一个其所捕获的变量的副本。
11、内联函数(以代码量的增加为代价,换取性能的提升)
背景:调用Lambda表达式或函数的过程是:程序要将执行顺序转移到被调用表达式或函数所在的内存地址,当被调用表达式或函数执行完之后,再返回到原函数执行的地方。
在这个转移的过程中系统要做如下事情:
-
为被调用的表达式或函数创建一个对象
-
为被调用的表达式或函数所捕获的变量创建一个副本
-
在跳转到被调用的表达式或函数所在的地址前,要保护现场并记录执行地址,以便调用结束之后的现场恢复,并按原来保存的地址继续,也就是通常所说的压栈和出栈。
可以看出,函数调用会产生一定的时间和空间开销,如果被调用的表达式和函数的代码量本身不大,而且会被经常的调用,那么这个时间和空间的开销就很不划算了。
因此,直接将被调用的表达式或函数的代码“嵌入”原来的执行流中 - 简单的说就是编译器负责去“复制、粘贴”:复制被调用的表达式或函数的代码,然后粘贴到原来的执行代码中。为了让编译器帮我们干这件事,可通过内联(inline)函数来实现。
=》如果被调用的表达式或函数包含大量的执行代码,则不应该使用内敛函数。因为每调用一次,就会复制一次,势必会造成代码激增。
noinline=》禁止内联