前言
在使用 Kotlin
进行开发时,我们不可避免的需要使用到 Standard.kt
内置的高阶函数:
对刚刚接触 Kotlin
开发的来说,使用的过程中难免会有些吃力,这里对 Standard.kt
中的标准函数做一些总结与使用归纳。
run() 与 T.run()
run()
方法存在两种:
public inline fun <R> run(block: () -> R): R {}
public inline fun <T, R> T.run(block: T.() -> R): R {}
第二种 run()
public inline fun <R> run(block: () -> R): R {}
分析:
- 要求传递的是一个代码块,同时返回一个任意类型
说明:但凡函数接收的是一个代码块时,使用的时候一般都建议使用 {}
来包含代码块中的逻辑,只有在一些特殊情况下可以参数 (::fun)
的形式进行简化
例如:
run {
println(888)
}
val res = run { 2 + 3 }
这没什么难度,这里我想要说的是:
<font color=red>但凡涉及到需要传递的代码块参数,都可以省略不传递,对于参数只是一个代码块的时候,可以直接用 ::fun
【方法】 的形式传递到 ()
中。</font>
啥意思?简单来讲,如果传递单代码块格式是 block: ()
这样的,我们可以这么干:
fun runDemo() {
println("测试run方法")
}
//我们可以这么干
run(::runDemo)
也就是说代码块格式为block: ()
这种的,用 ()
设置的方法必须是不含有参数的,例如上面的 runDemo()
方法就没有参数。
第二种 T.run()
public inline fun <T, R> T.run(block: T.() -> R): R {}
分析:
- 此处是执行一个
T
类型的run
方法,传递的依然是一个代码块, - 只是内部执行的是
T
的内部一个变量 或 方法等,返回的是 一个R
类型
val str = "hello"
val len = str.run {
length
}
上面例子,一个字符串 str
,我们执行 str
的 run
方法,此时在 run
方法中,我们可以调用 String
类中的一些方法,例如调用 length
返回的是一个 Int
类型结果。
这种在执行一个类的中多个方法的时候,并且要求返回一个结果的时候,使用这个run方法能够节省很多代码量。
同样的,对于方法传递的是一个代码块的函数而言,如果其传递的代码块格式是 block: T.()
这种,我们可以使用 ::fun
的形式传递到 ()
中,只是这个传递的方法要求必须含有一个参数传递。 说的很绕口,直接看代码:
val str = "hello"
str.run(::println)
//println函数
public actual inline fun println(message: Any?) {
System.out.println(message)
}
with()
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {}
分析:
-
with()
方法接收一个类型为T
的参数和一个代码块 - 经过处理返回一个
R
类型的结果 - 这个其实和上面的
T.run()
方法很类似,只是这里将T
传递到了with()
方法当中
val str = "hello"
val ch = with(str) {
get(0)
}
println(ch) //打印 h
同样的,这里代码块格式是 block: T.()
这种,因此根据上面说的规则,我们同样可以写成下面这样:
val ch2 = with(str, ::printWith)
fun printWith(str: String): Char? {
return if (str.isEmpty()) null else str[0]
}
什么场景下使用 with()
比较合适?下面代码中就很好的使用了 with()
方法简化了代码:
class Preference<T>(val context: Context, val name: String, val default: T, val prefName: String = "default") :
ReadWriteProperty<Any?, T> {
private val prefs by lazy {
context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
//注解消除警告
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (default) {
is String -> prefs.getString(name, default)
is Int -> prefs.getInt(name, default)
is Long -> prefs.getLong(name, default)
is Float -> prefs.getFloat(name, default)
else -> throw IllegalStateException("Unsupported data.")
} as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(prefs.edit()) {
when (value) {
is String -> putString(name, value)
is Int -> putInt(name, value)
is Long -> putLong(name, value)
is Float -> putFloat(name, value)
else -> throw IllegalStateException("Unsupported data.")
}
}.apply()
}
}
T.apply()
public inline fun <T> T.apply(block: T.() -> Unit): T {}
分析:
- 执行一个
T
类型中的方法,变量等,然后返回自身T
- 注意参数
block: T.()
,但凡看到block: T.() ->
这种代码块,意味着在大括号{}
中可以直接调用T内部的API
而不需要在加上T.
这种【实际上调用为this.
,this.
通常省略】
val str = "hello"
str.apply { length } //可以省略 str.
str.apply { this.length } //可以这样
//block: T.()格式代码块,因此同样可以这么写:
str.apply(::println)
实际开发中,通常配合判空 ?
一块使用,减少 if
判断,例如下面这样:
var str: String? = "hello"
//一系列操作后。。。
str?.apply(::println) ?: println("结果为空")
上面代码,如果字符串 str
不为空直接打印出来,如果为空则打印 结果为空
T.also()
public inline fun <T> T.also(block: (T) -> Unit): T {}
分析:
- 执行一个
T
类型中的方法,变量等,然后返回自身T
- 这个方法与上面的
apply
方法类似,只是在大括号中执行T
自身方法的时候,必须要加上 T. 否则无法调用T
中的API
,什么意思呢?看下面代码:
val str = "hello"
str.also { str.length } //str.必须加上,否则编译报错
str.also { it.length } //或者用 it.
上面代码中 {}
中使用了 it
来代替 str
,其实我们还可以手动指定名称:
//{}中的s代表的就是str
str.also { s -> s.length }
这就是also与apply的区别所在。
另外,需要注意的是also入参的代码块样式:block: (T)
,这种样式跟 block: T.()
一样,可以使用 ::fun
的形式传递到 ()
中,只是这个传递的方法要求必须含有一个参数传递。
因此我们可以这样操作:
str.also(::println)
T.let()
public inline fun <T, R> T.let(block: (T) -> R): R {}
分析:
let
方法与上面的 also
方法及其类似,只是 also
方法返回的结果是自身,而 let
方法是传递类型 T
返回另外一个类型 R
形式,因此在用法上也很类似:
var str:String? = "hello"
//...一堆逻辑执行后
val len = str?.let { it.length }
str.let(::println)
T.takeIf()
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
分析:
- 根据传递的参数
T
做内部判断,根据判断结果返回null
或者T
自身 - 传递的是【一元谓词】代码块,像极了
C++
中的一元谓词:方法只含有一个参数,并且返回类型是Boolean类型 - 源码中,通过传递的一元谓词代码块进行判断,如果是
true
则返回自身,否则返回null
看下使用代码:
val str = "helloWorld"
str.takeIf { str.contains("hello") }?.run(::println)
上面代码{}中判断字符串是否包含 "hello"
,是则返回自己,不是则返回 null,因此可以使用?来判断,如果不为null
,可以使用前面说的 run()
方法进行简单打印操作。
同样的,因为接收的代码块是一个一元谓词形式,因此,如果想要使用 (::fun)
方式来替代 {}
,则对应的函数方法必须满足两个条件:
- 返回值类型是
Boolean
类型 - 方法必须含有一个参数
因此可以写成下面这种:
val str = "helloWorld"
str.takeIf(::printTakeIf)?.run(::println)
fun printTakeIf(str: String): Boolean {
return str.contains("hello")
}
T.takeUnless()
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (!predicate(this)) this else null
}
分析:这个方法跟 takeIf()
方法类似,只是内部判断为false的时候返回自身T ,而 true
的时候返回 null
,因此不过多说明,使用参考 takeIf()
方法。
repeat()
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
分析:repeat
方法包含两个参数:
- 第一个参数int类型,重复次数,
- 第二个参数,表示要重复执行的对象
- 该方法每次执行的时候都将执行的次数传递给要被重复执行的模块,至于重复执行模块是否需要该值,需要根据业务实际需求考虑,例如:
//打印从0 到 100 的值,次数用到了内部的index
repeat(100) {
print(it)
}
//有比如,单纯的打印helloworld 100 次,就没有用到index值
repeat(100){
println("helloworld")
}
注意看传递的代码块格式:action: (Int)
,这就说明了要想使用(::fun)形式简化{}部分,需要代码块满足一个条件:
- 方法传递的参数有且只有一个
Int
类型或者Any
的参数
repeat(100, ::print)
repeat(100, ::printRepeat)
fun printRepeat(int: Int) {
print(int)
}
总结时刻
不管是 Kotlin
中内置的高阶函数,还是我们自定义的,其传入的代码块样式,无非以下几种:
-
block: () -> T
和block: () -> 具体类型
这种在使用(::fun)
形式简化时,要求传入的方法必须是无参数的,返回值类型如果是T则可为任意类型,否则返回的类型必须要跟这个代码块返回类型一致 -
block: T.() -> R
和block: T.() -> 具体类型
这种在使用(::fun)
形式简化时,要求传入的方法必须包含一个T类型的参数,返回值类型如果是R则可为任意类型,否则返回的类型必须要跟这个代码块返回类型一致。例如with
和apply
这两个方法 -
block: (T) -> R
和block: (T) -> 具体类型
这种在使用(::fun)
形式简化时,要求传入的方法必须包含一个T类型的参数,返回值类型如果是R则可为任意类型,否则返回的类型必须要跟这个代码块返回类型一致。例如let
和takeIf
这两个方法
只有搞清楚上面这三种代码块格式及其用法,对应的其他的一些例如 Strings.kt
中的 filter
、takeWhile
、flatMap
等一系列高阶函数,都能快速掌握。