天天看点

Kotlin研发第十三弹——高阶函数和lambda表达式

高阶函数和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):
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法则。

当调用变长参数的函数时,我们可以一个一个传递参数,比如

asList(1,2,3)

,或者我们传递一个array的内容给函数,我们就可以使用

*

前缀操作符:

val list= arrayOf("q","w","e","r")
    val changeResult= asList(1,2,3,"wyc",*list)
    println(changeResult)
           

继续阅读