Kotlin
标准库包含几个函数,其唯一目的是在对象上下文中执行代码块。当对提供了lambda
表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。从技术上讲,作用域函数在许多情况下是可以互换使用的。
上下文对象可用作参数(
it
),引用对象时使用it.
。返回值是lambda
表达式最后一行代码的结果。可用于调用调用链结果上的一个或多个函数。
var user = User()
// 使用此值作为参数调用指定的函数块并返回其结果。
val let = user.let {
it.name = "宾有为"
it.func = "let"
1 // 返回值 1
}
print("let:${user},return lambda result:${let}")
执行结果:
上下文对象可用作接收器(
this
),引用对象使用this.
或直接使用对象值。返回值是lambda
表达式最后一行代码的结果。run
执行与with
相同的操作,但作为上下文对象的扩展函数调用let
。当lambda
同时包含对象初始化和返回数值时,run
非常有用。
var user = User()
// 调用指定的函数块,将此值作为其接收器并返回其结果。
val run = user.run {
name = "宾有为"
func = "run"
1 // 返回值 1
}
print("run:${user}\nreturn lambda result:${run}")
执行结果:
非扩展函数:上下文对象作为参数传递,但在
lambda
内部,它作为接收器(this
)可用。返回值是lambda
表达式最后一行代码的结果。建议使用来调用上下文对象上的函数,而不提供lambda
结果。
var user = User()
// 以给定的接收器作为其接收器调用指定的函数块,并返回其结果。
val with = with(user) {
name = "宾有为"
func = "with"
1 // 返回值 1
}
print("run:${user}\nreturn lambda result:${with}")
执行结果:
上下文对象可用作接收器(
this
),引用对象使用this.
或直接使用对象值。返回值是对象本身。对于不返回值且主要对接收器对象的成员进行操作的代码块,请使用apply
。apply
的常见情况是对象配置。
var user = User()
// 使用此值作为其接收器调用指定的函数块,并返回此值。
val apply = user.apply {
name = "宾有为"
func = "apply"
}
print("also:${apply}\nreturn context object:${apply}")
执行结果:
上下文对象可用作参数(
it
),引用对象时使用it.
。返回值是对象本身。也适用于执行一些将上下文对象作为参数的操作。还可用于需要引用对象而不是其属性和函数的操作,或者不希望从外部范围隐藏此引用时。
var user = User()
// 使用此值作为参数调用指定的函数块并返回此值。
val also = user.also {
it.name = "宾有为"
it.func = "also"
}
print("also:${user}\nreturn context object:${also}")
执行结果:
takeIf
是类似if
关键字单个对象的过滤函数,使用方式与takeUnless
相反。使用对象进行调用时,如果该对象与lambda
的条件匹配,takeIf
将返回该对象。否则,返回null
。
var user = User(name = "宾有为", func = null)
val existName = user.takeIf { it.name != null }
val existSex = user.takeIf { it.func != null }
println("existName: $existName, existSex: $existSex")
执行结果:
takeIf
是类似else
关键字的过滤函数,使用方式与takeIf
相反。使用对象进行调用时,如果该对象与lambda
的条件匹配,takeIf
将返回该对象。否则,返回null
。
var user = User(name = "宾有为", func = null)
val existName = user.takeUnless { it.name != null }
val existSex = user.takeUnless { it.func != null }
println("existName: $existName, existSex: $existSex")
执行结果:
repeat
,是一个从0开始循环至指定长度的函数,与for (index in 0 until times) { }
执行的结果一致。
// 从0遍历至10
repeat(10){
print(it)
}
执行结果:
Kotlin
标准库包含几个函数,其唯一目的是在对象上下文中执行代码块。当对提供了lambda
表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。作用域函数有五个:let
、run
、with
、apply
和also
。
函数 | 使用场景 |
---|---|
let | 1、对非空对象执行lambda 2、在局部范围中引入表达式作为变量 |
with | 对对象的函数调用进行分组 |
run | 1、对象配置和计算结果 2、在需要表达式的地方运行语句 |
apply | 1、对象配置和计算结果 2、不返回值且主要对接收器对象的成员进行操作的代码块 |
also | 1、执行一些将上下文对象作为参数的操作。 2、需要引用对象而不是其属性和函数的操作 3、不希望从外部范围隐藏此引用时 |
在
kotlin
中,变量可以通过等号赋值,函数同样被允许使用等号进行赋值,这些使用等号赋值的函数就叫做简化函数。
简化函数的编写规范是有所要求的,如果函数表达式只有一行,才可以使用等号赋予函数其表达式。简化函数默认return
函数的最后一行代码。
fun main(args: Array<String>) {
println(test1())// result:2
println(test2())// result:简化函数
}
// 执行表达式 1+1
private fun test1() = 1+1
// 返回表达式"简化函数",简化函数如果有返回类型,表达式的执行结果必须是一个可以返回的类型。
private fun test2() : String = "简化函数"
kotlin
存在一种特殊的递归函数——尾递归函数,指的是函数末尾的返回值重复调用了自身函数。使用时只需要在函数的fun
前面加上tailrec
关键字,编译器在编译时会自动优化递归,使用循环方式代替递归,从而避免栈溢出的情况,以此提高程序性能。
fun main(args: Array<String>) {
print(factorial(5)) // result:120
}
// 求 i 的阶乘( i * i-1 )
tailrec fun factorial(i: Int): Int {
if (i != 1) {
return i * factorial(i - 1)
} else {
return i
}
}
把接收者类型,放到即将添加的函数前面,通过类可以对其调用的函数称为扩展函数。
如图所示,接收者类型写在了test
函数的前边,在扩展函数里,使用this
引用的是接收者l类型的对象,而非当前Test类。
如下图所示,通过String
类型还无法调用test
函数,我们在函数名称的前面加上String
类型,再次通过类型就可以引用test
函数。
扩展函数只能由接收者类型调用,不能通过扩展函数所在的类调用。
扩展函数不仅可以扩展函数,还可以扩展属性。
val String.lastIndex: Int
get() = 0
fun main(args: Array<String>) {
var a = "aaaa000"
print(a.lastIndex) // result:0
}
小知识
this
引用的是扩展函数的类型,而不是函数当前所在类。private
或protected
成员。高阶函数是将函数作为参数或返回函数的函数。
在以下的示例代码中,testFun1(i: Int, face: (String) -> String)
就是一个高阶函数,因为它接受一个函数值作为它的第二个参数。
fun main(args: Array<String>) {
testFun1(1) { it ->
// 实现业务逻辑,将it给return回去
it
}
}
// face: (String) -> String 等于 调用高阶函数使用的名称: (需要传递的参数类型) -> 返回类型
fun testFun1(i: Int, face: (String) -> String) {
// 接收到face返回值,并将其print
val value = face("testFun")
println("testFun:$value")
}
在实现高阶函数的lambda
表达式里(如图main
函数对testFun1
的调用),若设置有返回值,则默认return
最后一行的代码结果,若不设置,则返回Unit
。
运行结果:
高阶函数除了上述使用方式之外,还可以根据需求传入不同函数对逻辑进行处理。
fun main(args: Array<String>) {
val plusResult = num1AndNum2(20, 30) { n1: Int, n2: Int ->
// 执行运算
n1 + n2
}
println("$plusResult") // result:50
val minusResult = num1AndNum2(20, 30) { n1: Int, n2: Int ->
// 执行运算
n1 - n2
}
println("$minusResult") // result:-10
}
fun num1AndNum2(num1: Int, num2: Int, block: (Int, Int) -> Int): Int {
return block(num1, num2)
}
除此之外,高阶函数的lambda
表达式还可以拆分成lambda
表达式变量、匿名函数,分别以变量、匿名函数的形式传入高阶函数。
fun main(args: Array<String>) {
val minusResult = num1AndNum2(20, 30, lambda1)
println("$minusResult") // result:-10
val aaa = num1AndNum2(20, 30, lambda2) // result:50
println("$aaa")
val anonymous1 = num1AndNum2(20, 30, a) // result:-10
val anonymous2 = num1AndNum2(20, 30, b) // result:50
// 匿名函数
num1AndNum2(20,30,fun(x,y) = x + y) // result:50
}
// lambda表达式
val lambda1 = { x: Int, y: Int -> x - y }
// lambda表达式
val lambda2: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
// 匿名函数
val a = fun(x : Int, y : Int): Int = x - y
// 匿名函数
val b = (fun(x : Int, y : Int): Int = x + y)
fun num1AndNum2(num1: Int, num2: Int, block: (Int, Int) -> Int): Int {
return block(num1, num2)
}
lambda
表达式在底层被转换成了匿名内部类的实现方式,每调用一次lambda
表达式,都会创建一个新的匿名类实例,会造成额外的内存和性能开销,但这种开销可以通过inline
lambda
表达式来消除。使用inline
关键字修饰的函数也被称为内联函数。
使用inline
关键字,需结合反编译才能看见效果。
fun inlineFun(action: (() -> Unit)){
println("inlineFun: 调用前...")
action()
println("inlineFun: 调用后...")
}
fun main(args: Array<String>) {
inlineFun {
println("inlineFun: 正在调用...")
}
}
使用Idea、Android Studio开发工具反编译代码步骤:在开发工具的顶部菜单栏 Tools > Kotlin > Show Kotlin Bytecodes > Decompile。
inlineFun
未添加inline
函数代码反编译结果:
通过两次添加inline
关键字前后的反编译比对,可以看出inline
函数是将表达式转移到调用方,通过这样的方式减少lambda
创建新匿名类造成的开销。
当你尝试在没有lambda
表达式的函数上使用inline
时,编译器则会提示inline
对性能预期影响微不足道,应该结合高阶函数一起使用的警告。
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.
需要注意的是,inline
函数不支持修饰变量(包含持有高阶函数的变量),只能用于修饰高阶函数。
如果不希望传递给
inline
函数的所有lambda
都被内联,请使用修饰符noinline
标记一些不需要inline
的函数参数(仅限lambda
表达式)。使用noinline
的前提是使用的函数必须是inline
函数。
inline fun inlineFun(noinline action: (() -> Unit)){
println("inlineFun: 调用前...")
action()
println("inlineFun: 调用后...")
}
fun main(args: Array<String>) {
inlineFun{
println("inlineFun: 调用中...")
}
}
添加noinline
代码反编译后,“调用中…”的字符并没有被反编译出来,而是用action$iv.invoke();
调用函数。
crosinline
用于禁止传递给内联函数的lambda
中的非局部返回。
在内联函数的lambda
表达式里,使用return
会中断高阶函数后面代码的执行,讲inline
函数有讲解到:inline
函数是将表达式转移到调用方,因此下面代码的“调用后”是不会print
。
inline fun inlineFun(action: (() -> Unit)) {
println("inlineFun: 调用前...")
action()
println("inlineFun: 调用后...")
}
fun main(args: Array<String>) {
inlineFun {
println("inlineFun: 调用中...")
return
}
}
使用crossinline
关键字,则可以杜绝这种情况。inline
函数添加crossinline
后,return
将会报'return' is not allowed here
的错误。
部分博客把crossinline
的含义解释成“检查代码中是否有return
,如果有则会执行不通过”。这样的说法是片面的,官方文档已经解释了,是禁止非局部返回。如果高阶函数里面的return
不影响到嗲用高阶函数的函数后面代码执行,是可以使用return
,如下:
省略了函数名字的函数称之为匿名函数。
匿名函数往往结合高阶函数一起使用,匿名函数默认隐式return
最后一行代码,写法有以下三种:
val a = fun(str: String): String = "a"
val b = (fun(str: String): String = "b")
参数和返回类型的指定方式与高阶函数相同,如果可以从上下文中推断出参数类型,则可以省略参数类型。如下:
// 匿名函数
println(fun(item) = "c")
// 高阶函数
fun println(str : (String) -> String){
println(str("ttt"))
}
匿名函数与lambda
表达式类似,写法不同,但执行效果可以是一致的,如下lambda
函数等同于上面的匿名函数写法:
var d: (it: String) -> String = { "d" }
var e: (String) -> String = { "e" }
var f = { it: String -> "f" }
看了好几篇的博客,大部分都是说lambda
表达式就是匿名函数,我在kotlin
官方文档找到的匿名函数写法并不包含lambda
表达式,同时也不知道其它作者所表达的lambda
表达式就是匿名函数的依据来自于哪里。
在官方文档的匿名函数介绍里,官方讲解了匿名函数与lambda
表达式的区别:lambda表达式和匿名函数之间的另一个区别是非本地返回的行为。没有标签的return语句总是从用fun关键字声明的函数返回。这意味着lambda表达式内的返回将从封闭函数返回,而匿名函数内的返回则从匿名函数本身返回。
小结
lambda
表达式。参考文档
1、高阶函数
2、标准、作用域函数
3、Kotlin之高阶函数
4、《Kotlin从零到精通》—— 函数的运用
5、Android筑基,Kotlin扩展函数详解——郭霖
6、Kotlin Doc——Extensions
7、Kotlin Keywords and operators