前言
一、函数使用
1.中缀标记法(infix notation)
(1) 咱们一直使用的user.foo(12),是使用的点号标记法(dot notation),这里我们学一个中缀标记法。
中缀标记法必须满足以下条件
- 是成员函数或者是扩展函数
- 只有单个参数
- 使用infix标记
infix fun iFoo(x:Int){ //必须要有一个参数
}
user iFoo 12 //等价于 user.iFoo(12)
(2) 函数参数可以指定默认值, 当参数省略时, 就会使用默认值, 这种功能使得我们可以减少大量的重载(overload)函数定义.
fun foo(x:Int,y:Int=0,z:Int=0){}
user.foo(1) //等价于user.foo(1,0,0)
user.foo(1,2) //等价于user.foo(1,2,0)
user.foo(1,z=1) //等价于user.foo(1,0,1)
(3) 不定数量参数(Varargs)
fun foo(vararg x: Any) {
for (i in x) {
print("$i ")
}
println()
}
user.foo(1,2,3,4) //输出1 2 3 4
val a = arrayOf(5, 6, 7)
// 展开操作符(在数组之前加一个*)
user.foo(*a) //输出5 6 7
2. 尾递归函数(Tail recursive function)
Kotlin 支持一种称为 尾递归(tail recursion) 的函数式编程方式. 这种方式使得某些本来需要使用循环来实 现的算法, 可以改用递归函数来实现, 但同时不会存在栈溢出(stack overflow)的风险. 当一个函数标记为tailrec , 并且满足要求的形式, 编译器就会对代码进行优化, 消除函数的递归调用, 产生一段基于循环实现 的, 快速而且高效的代码.
tailrec fun findFixPoint(x:Double=1.0):Double=
if (x==Math.cos(x)) x else findFixPoint(Math.cos(x))
要符合 tailrec 修饰符的要求, 函数必须在它执行的所有操作的最后一步, 递归调用它自身. 如果在这个递 归调用之后还存在其他代码, 那么你不能使用尾递归, 而且你不能将尾递归用在 try/catch/finally 结构内. 尾 递归目前只能用在 JVM 环境内
3. 带有接受者的函数字面值
sum : Int.(other: Int) -> Int
调用
1.sum(2)
二、lambda表达式
- Lambda 表达式用大括号括起,
- 它的参数(如果存在的话)定义在 -> 之前 (参数类型可以省略)
- (如果存在 -> 的话)函数体定义在 -> 之后.
val sum = { x: Int, y: Int -> x + y }
三、匿名函数
匿名函数看起来与通常的函数声明很类似, 区别在于省略了函数名。
匿名函数示例:
fun(x: Int, y: Int): Int {
return x + y
}
// 使用匿名函数:
(1..100).filter { it % 3 == 0 }
(1..100).filter { a -> a % 3==0 }
(1..100).filter(fun(item) = item % 3 == 0)
四、高阶函数
定义:高阶函数(higher-order function)是一种特殊的函数, 它接受函数作为参数, 或者返回一个函数。
举个例子:
fun <T> with(t: T, body: T.() -> Unit) {
t.body()
}
这个函数接收一个 T 类型的对象和一个被作为扩展函数的函数body。它的实现仅仅是让这个对象去执行这个函数(这里执行函数body,在调用的时候实现这个函数body)。因为第二个参数是一个函数,所以我们可以把它放在圆括号外面,所以我们可以创建一个代码块,在这这个代码块中我们可以使用 this 和直接访问所有的public的方法和属性:
with(movie){
titleTxt="$title"
}
调用时传入了一个Movie对象,和实现的body函数{ titleTxt="$title"}。
例如lock()函数
/**
* body 参数是一个函数类型: () -> T, 因此它应该是一个函数, 没有参数, 返回一个T类型的值
* body 函数在try块内被调用, 被lock锁保护住, 它的执行结果被lock()函数当作自己的结果返回.
*/
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body() //这里调用函数{1*2},返回结果2
} finally {
lock.unlock()
}
}
val l = lock(lock){
1 * 2 //如果有返回值,返回值为函数块最后一行,在这里就是1*2的结果
}
在比如,下边这个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
}
fun main(){
val result = listOf(1,2,3).map { a->a*2 }
//等价于,val result = listOf(1,2,3).map { it*2 } // 如果不自己命名为a,则默认为it
}
五、内联函数
使用 高阶函数 在运行时会带来一些不利: 每个函数都是一个对象, 而且它还要捕获一个闭包(在函数体内部访问的那些外层变量). 内存占用(函数对象和类都会占用内存) 以及虚方法调用都会带来运行时的消耗.
1. inline
一个内联函数会在编译的时候被替换掉,而不是真正的方法调用。这在一些情况下可以减少内存分配和运行时开销。举个例子,如果我们有一个函数,只接收一个函数作为它的参数。如果是一个普通的函数,内部会创建一个含有那个函数的对象。另一方面内联函数会把我们调用这个函数的地方替换掉,所以它不需要为此生成一个内部的对象。
例如:
inline fun supportsLollipop(code: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
code()
}
}
supportsLollipop {
window.setStatusBarColor(Color.BLACK)
}
编译时,代码就是这样子的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(Color.BLACK)
}
2. noinline
如果一个内联函数的参数中有多个 Lambda 表达式, 而你只希望内联其中的一部分, 你可以对函数的一部分参数添加 noinline 标记
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { // ...
}
注:函数内联也许会导致编译产生的代码尺寸变大, 但如果我们使用合理(不要内联太大的函数), 可以换来性能的提高