高阶函数和lambda表达式
尾递归函数(tailrec)
kotlin支持函数时编程的尾递归。这个允许一些算法可以通过循环而不是递归解决问题,从而避免了栈溢出。当函数被标记为
tailrec
时,编译器会优化递归,并用高效迅速的循环代替它
//尾递归
tailrec fun findFixPoint(x:Double=1.0):Double=if (x==Math.cos(x))x else findFixPoint(Math.cos(x))
这是计算余弦不动点,输出结果
0.7390851332151607
**tailrec:**符号使用后,最后一个操作只能是调用函数自己,
不允许在递归代码后不允许有其他代码的
不允许在try/catch/finally块中
当前的尾递归只能在JV买的后端中用
高阶函数 ?
高阶函数就是可以接受函数作为参数或者返回一个函数的函数。
比如
lock()
就是例子,接受一个lock对象和一个函数,运行函数并释放lock
//高阶函数
fun <T> lock(lock: Lock,body:()->T):T{
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
如果我们想调用
lock()
函数,我们可以传给他另外一个函数作为参数
fun toBeSynchroized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchroized)
其中最方便的办法是传递一个字面函数(通常为lambda表达式):
字面函数
字面函数被包括在大括号里
参数在
->
前面声明(参数类型可以省略)
函数体在
之后
->
在kotlin中有一个约定,如果某一个函数的最后一个参数是函数,并且你向那个位置传递一个lambda表达式,那么你可以在括号外面定义这个lambda表达式
lock(lock){
sharedResource.operation()
}
map()
(ofMapReduce):
map()
fun <T,R> List<T>.map(transform:(T)->R):List<R>{
val result= arrayListOf<R>()
for (item in this){
result.add(transform(item))
}
return result
}
val doubled= arrayListOf(1,2,3).map { it->it*2 }
println(doubled)
如果字面只有一个参数,声明可以省略,名字叫就是
it
:
val doubled= arrayListOf(1,2,3).map { it*2 }
println(doubled)
输出结果
[2, 4, 6]
并且可以写LINQ-风格的代码
val strings= arrayOf("a","abc","abcd","qww")
val test=strings.filter { it.length==3 }.sortedBy { it }.map { it.toUpperCase() }
for (ite in test){
println(ite)
}
输出
ABC
QWW
内联函数
**内联函数时为了提高高阶函数的性能:**使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,且这个对象捕获了一个闭包,闭包内的变量可以在函数对象内部访问。内存分配(为函数对象和类)和实际调用将引入运行时间开销。
但通过使用内联
lambda表达式
,可以避免这些情况出现。
内联函数如何运作
当一个函数被声明
inline
时,它的函数体是内联的,也就是说,函数体会被直接替换到函数调用的地方,如下例子:
//内联函数
inline fun inlineTest(prefix:String,action:()->Unit){
println("call before $prefix")
action()
println("call after $prefix")
}
//内联函数
inlineTest("douwowan"){
println("hahhah")
}
输出
call before douwowan
hahhah
call after douwowan
上面所述代码就相当于直接输出——编译器编译时显示成这样:
println("call before douwowan")
action()
println("call after douwowan")
高阶函数的方式——传递函数类型的变量作为参数
val call:()->Unit={ println("hahaha")}
inlineTest("都一样",call)
inlineTest("差不多",{println("hahaha")})
输出
call before 都一样
hahaha
call after 都一样
call before 差不多
hahaha
call after 差不多
参数
函数参数是用Pascal符号定义的
name:type
。参数之间用都好隔开,每个参数必须指明类型。
默认参数
函数参数可以设置默认值,当参数被忽略时会使用默认值,
这样比其他语言可以减少重载
默认值可以通过type类型后使用
=
进行赋值
命名参数
在调用函数时可以参数命名,这对于那种有大量参数的函数很方便
fun reformat(str:String,isMale:Boolean=true,age:Int=27){...}
TestResourceLoader().reformat("wyc")
使用时可以使用默认的参数来初始化方法不用过分重载,适用于某些含有默认属性的方法
注意:命名参数语法不能用于调用Java函数中,因为Java的字节码不能确保方法参数命名的不变性
不带返回值的参数
如果函数不会反悔任何有用值,那么他的返回类型就是
unit
.并且可以省略
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` or `return` is optional
}
单表达式函数
当函数只返回单个表达式,大括号可以省略并在
=
后面定义函数体
在编译器可以推断出返回值类型的时候,返回值可以省略
明确返回类型
下面的例子中必须有明确返回类型,除非他是返回
Unit
类型。kotlin并不会对函数体重的返回类型进行推断,因为函数体中可能有复杂的控制流,他的返回类型未必对读者课件(甚至编译器而言也有可能是不可见的)
变长参数
函数的参数(通常是最后一个参数)可以用
vararg
修饰符进行标记:
//可变长度参数
fun <T> asList(vararg ts:T):List<T>{
val result=ArrayList<T>()
result.addAll(ts)
return result
}
println(asList(1,2,3,"wyc"))
输出结果
标记后允许传递可变长度的参数
注意,只有一个参数可以被标注 vararg
。加入 vararg
并不是列表中的最后一个参数,那么后面的参数需要通过命名参数语法进行传值,再或者如果这个参数是函数类型,就需要通过lambda法则。
vararg
vararg
当调用变长参数的函数时,我们可以一个一个传递参数,比如
asList(1,2,3)
,或者我们传递一个array的内容给函数,我们就可以使用
*
前缀操作符:
val list= arrayOf("q","w","e","r")
val changeResult= asList(1,2,3,"wyc",*list)
println(changeResult)