函数类型
kotlin中函数不仅仅扮演了执行者的身份,他将参与到数据传递这条反应链之中。实际上在java中,我们也有这种的操作,那就是callback。仔细想想,所有的回调实际上目的都是把函数当做参数传递过去。
PS: 题外话,我们都知道面向对象编程,再此之前是面向过程编程,那么在此之后呢?未来的趋势是什么并不清楚,但是就现在来说,函数式编程的思想正在快速传播(虽然它由来已久)。个人认为java8的lambda特性的添加就是java在函数式编程中做出的妥协或者说顺应时代潮流。当然c++还是老顽固的样子,并不是很支持函数式编程,不过并不是说c++不能使用函数式编程,至少函数指针还是有的。
回到主题上,kotlin中有一种特殊的类型,叫做函数类型,比如我们可以定义一个函数类型的变量
var listener:((a:Int)->Int)? = null
提取其中的类型 ((a:Int)->Int)? 其中?的作用是表示能够为空,实际上也是修饰符,再拨出来,所以真是的类型是 (a:Int)->Int 和java的lambda很像对不对, ->前面的部分表示参数, ->后面的部分表示返回值。所以listener就表示一个 输入一个int类型参数,返回一个int型值的函数。
var listener:((a:Int)->Int)? = null
var callback:((a:Int,b:Int)->Int)? = null
val plus = {x: Int ,y: Int -> x+y}
fun lambdaTest(){
listener = ::sum
listener = temp@{return@temp it+1}
listener = {it+1}
callback = {a, b -> a+b}
callback = {_, b -> b}
}
fun sum(x:Int):Int{
return x+1
}
比如上面,看listener的赋值方式,我们找到了三个赋值的方法。使用一个现有方法,加上:: 就能将该方法当做值赋值给变量。
第二种和第三种本质当都是匿名方法,区别在于temp@ 为什么要添加这个呢? 如果这个方法体不是一行能解决的,那就一定要这个了,只有表达式等于返回内容的时候可以省略return。
还有一个就是 it 这个东西,如果这个函数类型只有一个输入值,那么我们可以省略输入变量的定义,直接使用it来代表这个参数,如果是过个参数,那么就要列出参数名了,比如像callback这样的。
再来看 _ 符号,这个符号表示,我不关心这个参数,我在方法中不会使用它,所以使用_来表示参数名。
然后是plus函数变量的定义,我们省略了类型,所以这个时候就需要在定义的时候显示指定x,y的类型(如果没有省略变量类型,那么定义的时候就能省略参数类型)。
函数类型参数
既然有了函数类型的变量,那么理所当然也有函数类型的参数,实际上函数类型的参数在实际使用中会更多一点。
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
val result = lock(lock, { sharedResource.operation() })
基本的使用方法和函数类型变量没有区别。
不过有一个有趣的地方在于我们可以这样
lock (lock) {
sharedResource.operation()
}
当函数类型参数是最后一个变量的时候,我们可以将他提到括号外面!如果这个时候没有其他参数,我们甚至可以省略括号。
lambda表达式
好了,我们要介绍lambda表达式了!
如上
好了,lambda表达式介绍完了。
???????什么情况。没错,你已经学会使用lambda表达式了, 那个带 -> 的奇怪东西就是lambda表达式,而且你已经看到了他的用法。
匿名表达式
上面的lambda表达式,比如 {a,b-> a+b } 这个么表达式,我们没有指定它的返回值。这是lambda通过推断自己填上的返回类型。如果我们不希望这样,那么就需要使用匿名表达式了
fun highFun(listener:()->Int){
}
highFun(fun():Int {
var a = 1
return a
})
highFun(fun():Int = 1)
highFun(fun() = 1)
和普通函数一样啊,只是不用名字了而已。这种匿名函数也能够当做参数直接传递给函数变量参数。
这里有个有趣的地方,就是lambda表达式和匿名函数的区别。区别在于return,只需要记住一个点,return就是寻找最近的fun关键字,退出上一层fun。比如匿名函数有fun,所以return 直接退出匿名函数,但是lambda没有fun的,所以想要退出lambda就需要使用@符号。
一种奇特的类型
我们之前有介绍过额外方法
fun Int.test(other:Int):Int{
return this + other
}
比如这样,我们在int类型上添加一个额外方法。
现在,我们可以将这个额外方法当做一个类型:就问你怕不怕
var test2:Int.(other:Int)->Int = {other -> this+other}
比如这样,我们定义可一个test2的变量,类型是 Int.(other:Int)->Int 相比于普通的函数类型多了一个Int.的限定符。这就表示,这个方法是用在Int上的,给int添加一个额外方法。
我们可以像使用test一样使用test2
1.test(2)
1.test2(2)
他们的效果是一样的。
关于test2,我们还使用了lambda表达式来给这个东西赋值。参考上面的匿名方法,我们也可以使用匿名方法来赋值
var test3:Int.(other:Int)->Int = (fun Int.(other:Int):Int = this + other)
效果是一样的。
相比于额外方法,它是一个类型,既然是类型,就能当做参数,比如
fun lambdaTest2(aa:Int.(other:Int)->Int){
1.aa(2)
}
lambdaTest2(test2)
更坑爹的事情在于 当一个参数是 (Int,Int)->Int类型的时候,我们可以使用 Int.(Int)->Int来凑数
fun lambdaTest3(aa:(Int,Int)->Int){
aa(1,2)
}
lambdaTest3(test2)
内联函数 inline
在java中,我们可能对于inline并不重视,用的也不多,但是在kotlin中,inline的作用就大大增加了。为什么?对于所有的高阶函数(拥有函数参数的方法),在编译运行的时候其实会比较耗时,kotlin会将函数参数自动生成一个类,然后在运行的时候还需要创建一个类,并且调用类的方法。我们知道这是耗时的操作。
fun inLine(sum:(Int)->Int){
Log.d("sss","haha"+sum(1))
}
inline fun inLine2(sum:(Int)->Int){
Log.d("sss","haha"+sum(1))
}
fun inLinePlus(base:Int):Int = base+1
inline fun inLinePlus2(base:Int):Int = base+1
fun inLineTest(){
inLine(::inLinePlus)
inLine { it+1 }
inLine2(::inLinePlus)
inLine2 { it+1 }
}
//编译后
public static final void inLineTest() {
inLine(InlineTestKt$inLineTest$1.INSTANCE);
inLine(InlineTestKt$inLineTest$2.INSTANCE);
Log.d("sss", "haha" + inLinePlus(1));
Log.d("sss", "haha" + 2);
}
PS:以上是编译前后的代码
所以和java一样,inline就应运而生了。inline的性质倒是没有区别,直接使用代码替换函数调用的地方。
noinline
贱人就是矫情……有些时候我们可能需要让inline函数中某个函数参数非内联
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
那么就在这个参数上面添加noinline的关键字……
non-local return
前面有提到过,对于普通的高阶函数,在函数参数中直接调用return时错误的,需要添加lable,但是对于inline函数,我们可以直接调用return
fun inLine(sum:(Int)->Int){
Log.d("sss","haha"+sum(1))
}
inline fun inLine2(sum:(Int)->Int){
Log.d("sss","haha"+sum(1))
}
fun inLineTest(){
inLine { return } //错误
inLine2 { return } //正确,return 会退出inLineTest
}
在inline中有一种情况,我传入的函数参数包裹在其他方法调用中,那么就需要添加crossinline标记,防止body方法中的return作用歧义。
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
PS:只有在inline方法中会出现错误,如果你不知道什么时候需要添加crossinline不要着急,编译器会提示错误,并且提示你添加关键字的
reified type parameters
open class Base2{
}
class TreeNode:Base2(){
var parent:TreeNode? = null
}
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p.parent
}
@Suppress("UNCHECKED_CAST")
return p as T?
}
fun inLineTest(){
var treeNode:TreeNode = TreeNode()
treeNode.findParentOfType(Base2::class.java)
}
定义一个TreeNode类,用来保存树的节点,然后定义一个findParentOfType方法,用以搜索TreeNode对象的父节点直到是类型T为止。
为什么我们需要传入Base2的class类型进去呢?因为我们无法使用 p is T 这样的类型判断。
不过对于Inline方法,我们还是有补救方法的,在T前加上前缀reified
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && !(p is T)) {
p = p.parent
}
@Suppress("UNCHECKED_CAST")
return p as T?
}
treeNode.findParentOfType<Base2>()
这样就不需要传入class参数,能够使用is来判断。
甚至,我们还可以这样用
inline fun <reified T> membersOf() = T::class.members
T就真的像一个类型一样了,而不仅仅是一个通配符。
inline属性
我们知道kotlin中的属性访问默认使用get 和set ,但这也是方法调用,会消耗开销,所以kotlin也提供了内联属性
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
inline var bar: Bar
get() = ...
set(v) { ... }