稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

More About Kotlin Functions

Kotlin中的函数是一级对象,除了常规的函数式编程以外,还支持一些非常灵活的特殊用法,可以大大增强代码的可读性和简洁性,让代码更加的优雅,在业界顶级的库如Compose中有大量的应用,今天就来学习一些,以扫清学习Compose的障碍。

Extension Functions

与传统的编程语言如C/C++,Java或者Python最大的不同就是,Kotlin对于类的扩展提供了相当灵活的方式。像Java和Python除了标准的继承方式以外,就只能用注解和Decorator。但对于Kotlin还可以用Extensions这一方式。无论是注解还是Decorator,它的使用方式还是比较笨拙的,可以明显的看出来是额外定义的函数,与原Class是没啥关系的。

比如说,对于整数来说,我们通常会有求绝对值,通常可以这样写:

1
fun abs(a: Int) = if (a < 0) -a else a

然后,这样使用:

1
val aa = abs(a)

但在Kotlin中,有更优雅的方式:

1
2
3
4
fun Int.abs() = if (this < 0) -this else this

println((-4).abs())
println(100.abs())

这就是Extension functions,这样定义了后,可以像整数类型本身定义的方法那样直接在其对象上面调用。

如何定义Extension functions

Extension functions是针对Class的,或者一个Type的,指定目标Class名字,和参数就可以了,在函数的内部this就是调用函数时的对象。

1
2
3
4
fun <ClassName>.<function name>(params...): return type {
  // this is the function's receiver, which is the object when function invoked.
  // function implementation
}

需要注意,Extension functions必须是针对Class的。

理解Extension functions

Extension functions并没什么高深和神秘的东西,它只是相当于一个static函数,接收目标Class的对象而已,比如说:

1
2
3
fun Shape.area(): Int = this.length * this.width

fun area(shape: Shape): Int = shape.length * shape.width

其实这两个函数是完全一样的,上面的那个Extension function其实就相当于后面的那个常规函数。只不过在函数的调用上面更加的方便,看起来更像是目标Class提供的方法一样,更优雅一些。

Extension function的作用域

Extension function并不会真的对目标Class做任何修改,它只是相当于你自己定义的一个函数。所以,它的作用域就是你定义的函数的作用域,如果你是在一个文件中定义的,那么它的作用域就是导入了这个文件的地方;如果是在一个类中的,那作用域就是这个类。

另外的问题就是,假如在多个地方定义了相同的Extension function,会发生什么呢,相同的意思就是目标Class一样,函数名字也一样,所做的事情也一样,仍是把它当成普通函数来理解就行,按照虚拟机懒惰加载的原则,应该是第一个被引用到的Extension function生效。

参考资料

Infix Functions

准确的来说是Infix notation,它是一种执行函数的特殊方式,并不是定义了特殊的函数。也就是说某个函数被infix修饰了后,就可以用更为简洁的方式来调用它。常规的函数执行(或者说调用)是用函数名字加上括号,括号里面是参数,比如foo(),bar(“here”)。而infix方式则可以是 参数1 函数名 参数2 这种方式,也即与常规的函数调用完全不一样。看起来像是语言本身的关键字一样。

比如,移位并不是运算符,也不是关键字,而是一个被infix修饰的二元参数函数:

1
2
3
4
finfix un Int.shr(x: Int): Int {...}

8.shr(2) // 这样正常调用也完全可以,把整数8右移2位
8 shr 2 // 这是infix式的用法,其实是等同于上面的函数调用

infix必须是Extension function,并且只能有一个参数,算上Extension function的接收对象,其实一共是2个参数。标准库中也定义了大量的infix,如整数位移的shr和shl。以及像一些DSL中的函数,都会定义成infix,以让代码更简洁。

总之,下次再见到 a xyz b 这种写法时,不用害怕,并不是有了新的关键字,这里的xyz是infix notation,把它当成函数调用 xyz(a, b) 就好了。

参考资料

inline Functions

常规的lambda会有closure(捕获上下文中的对象),在编译后会产生很多对象,这会导致一些性能问题,但这是标准的函数式编程。

但某些情况下,我提供的是一个单纯的行为(lambda),比如像Collections的forEach以及filter,传入的lambda也好,或者其他函数也好,这是一个单纯的行为,你在集合中遍历时执行它就好。这种情况好,我们希望高阶函数在其函数体内直接使用传入的函数参数就可以了,不需要进行常规的对象创建(closure对象以及函数接口对象)。就可以使用inline关键字来修饰这人高阶函数。

参考资料

Comments