阅读 1818

那天我遇到工具类的那些事

某一天,在QQ群聊起用KotlinSharedPreferences的封装,然后有大佬贴出了他的代码,一行代码实现取值,然后再一行代码实现存值,最后他傲娇的说道:“看见没,这一招就叫做委托代理!”

// 取值
var a by PreferenceUtil("key","a")
// 存值
a = "b"
复制代码

作为菜鸟的我立即惊为天人,于是对委托代理展开了学习。

(当然,如果你不打算看我啰嗦这么多,也可以直接看最后传送门的源码!)

1、什么是委托代理?

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

1.1 静态代理

看完这句定义,我表示一脸懵逼,这说的是啥?于是我接着往下看这个UML图,想了解多一点信息。

委托代理——UML图

看完上面这个图,有一点点明白了:

公司发布一项任务:开发软件,于是我把这个任务领取以后,转交给第三方。这样第三方和我都拥有这个任务,但是我实现的方式是通过第三方实现,第三方实现的方式还是第三方自个想办法实现,至于任务报酬嘛,呵呵!!

// 任务
interface Task{
    fun develop()
}

// 第三方
class Person :Task{
    override fun develop() {
        println("我写了一个支付宝,欢迎你们来存钱")
    }

}

// 我接收任务了
class Custom(val task:Task):Task{

    // 我通过第三方完成任务
    override fun develop() {
        println("我把任务外包出去了,这个月我又是优秀员工!哈哈")
        task.develop()
    }

}

// 公司发布任务
class Company{
    fun main(){
        // 这是一个接私活的
        val person = Person()
        // 我领取公司任务了
        val custom = Custom(person)
        // 我完成公司任务了
        custom.develop()
    }
}

复制代码

一想到我赚钱啦就高兴,但是上面这个代理咋个和前面大佬的那个代理不一样呢?前面大佬的代理是通过by关键字实现的,这里咋就自个实现了呢?我遇到“假的代理”了吗?我们再看看这里面还有什么花样?

1.2 动态代理

代理模式大致分为两部分,一是静态代理,二是动态代理,静态代理如上述示例那样,代理者的代码由程序员自己或通过一些自动化工具生成固定的代码再对其进行编译,也就是说在我们的代码运行前代理类的class编译文件就已经存在,而动态代理则与静态代理相反,通过反射机制动态的生成代理对象。也就是说我们在code阶段,压根就不需要知道代理谁,代理谁我们将会在执行阶段决定,而Java也给我们提供了一个便捷的动态代理接口InvocationHandler,实现该接口需要重写其调用方法invoke()

看上去动态代理好像比静态代理高级多了,但是我也没有弄大明白,继续用上面的示例来说一下我理解的说法:

由于互联网寒冬,公司很久没有发布新任务了,我也没有任务给第三方,于是第三方都饿死了。这天公司突然发布了一个大任务,我一时激动又给抢到手了。这个时候才发现没有人帮我开发了。于是我通过面向群友开发,他们给我一个建议:不用提前联系第三方,在每次接到任务以后,直接在群里随机找一位大佬来开发,我还是可以继续做优秀员工。

// 任务
interface Task{
    fun develop()
}

// 我的新做法
class NewCustom(val obj:Any): InvocationHandler {
    // 第三方暂定为非具体的对象,执行任务的时候再决定
    override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any {
        return method.invoke(obj, args)
    }

}

// 群里的大佬
class DaLaoA:Task{
    override fun develop() {
        println("这次给大家写了一个微信聊天工具,钱包功能做得特别棒,欢迎体验 !")
    }

}

// 公司
class Company{
    // 发布任务New
    fun main(){
        // 今天大佬A有空(明天不确定了)
        val person = DaLaoA()
        // 我领取公司任务了
        val custom = NewCustom(person)
        // 临时通知大佬
        val task = Proxy.newProxyInstance(person::class.java.classLoader, arrayOf(person::class.java),custom) as Task
        // 大佬动手
        task.develop()
    }
}
复制代码

这个好像确实牛逼,但是为什么还是没有by呢?难道摆不平了?算了,继续请教大佬!

1.3 Kotlin 委托

我找到大佬,请大佬喝奶后,大佬告诉我道:

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。 Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

1.3.1 类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

以上面的Custom代码演示

// 我接收任务了
class Custom(val task:Task):Task by task{
    
}
复制代码

上面的代码即是将Custom对象的develop请求委托给task这个对象来进行处理

1.3.2 属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

class Item{
    var password:String by EncryptTool()
}
class EncryptTool{
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "这是一个解密后的密码"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("这里对密码进行了加密")
    }
}
复制代码

上面的内容,下面简单解释一下:

字段 含义
var/val 为属性的可读与只读,对应了代理类的是否需要setValue方法。val不需要,var需要
password 则是属性名称
String 属性类型
by 指定关键字
EncryptTool() 委托代理类的表达式
thisRef 被委托的类的对象(上面代码则是Item对象)
property 被委托的属性的对象(上面代码则是password对象)

原来大佬给我的代码是用的这个属性委托,难怪这么厉害!但是我看了上面的UML图以后,觉得有点眼熟,于是我又去看了一下装饰模式和外观模式。

2. 那些似曾相识的设计模式

  • 装饰器模式

  • 外观模式

为什么会觉得这些设计模式似曾相识呢,是因为他们都是通过某个(或者某些)类实现接口(或者继承类),然后通过操作这些类,来实现自己想要达到的预期效果。

2.1 装饰器模式

CircileRectangle通过实现Shape接口分别获得了绘制的功能,然后ShapeDecorator通过“包裹”Circile(或者Rectangle)来绘制圆(或者矩形),最后RedShapeDecorator又通过“包裹”ShapeDecorator来实现绘制一个红色边框。

interface Shape {
    fun draw()
}

class Circle:Shape{
    override fun draw() {
        println("Shape: Circle")
    }

}

class Rectangle:Shape{
    override fun draw() {
        println("Shape: Rectangle")
    }

}

open class ShapeDecorator(private val decoratedShape: Shape):Shape{
    override fun draw() {
        decoratedShape.draw()
    }

}
class RedShapeDecorator( decoratedShape: Shape):ShapeDecorator(decoratedShape){
    override fun draw() {
        super.draw()
        println("Border Color: Red")
    }
}
class DecoratorPatternDemo{
    fun main(){
        val circle: Shape = Circle()
        val rectangle = Rectangle()
        val redCircle: ShapeDecorator = RedShapeDecorator(circle)
        val redRectangle: ShapeDecorator = RedShapeDecorator(rectangle)
        println("Circle with normal border")
        circle.draw()

        println("Circle of red border")
        redCircle.draw()

        println("Rectangle of red border")
        redRectangle.draw()
    }
}
复制代码

执行结果:

Circle with normal border
Shape: Circle

Circle of red border
Shape: Circle
Border Color: Red

Rectangle of red border
Shape: Rectangle
Border Color: Red
复制代码

2.2 外观模式

外观模式装饰器模式有很大的相似之处,后者通过一层层叠加,来实现上面的画圆 -> 画边框;前者直接持有画圆、画矩形、画正方形的对象,然后根据需要随意顺序的绘制圆 -> 矩形 -> 正方形,当然你还可以再绘制一个边框。

interface Shape {
    fun draw()
}

class Circle:Shape{
    override fun draw() {
        println("Shape: Circle")
    }

}

class Rectangle:Shape{
    override fun draw() {
        println("Shape: Rectangle")
    }

}

class Square:Shape{
    override fun draw() {
        println("Shape: Square")
    }
}

class ShapeMaker{
    private val circle =  Circle()
    private val rectangle = Rectangle()
    private val square = Square()

    fun drawCircle() {
        circle.draw()
    }

    fun drawRectangle() {
        rectangle.draw()
    }

    fun drawSquare() {
        square.draw()
    }
}

class FacadePatternDemo{
    fun main(){
        val shapeMaker = ShapeMaker()

        shapeMaker.drawCircle()
        shapeMaker.drawRectangle()
        shapeMaker.drawSquare()

        println("I'll draw a border in a second")
    }
}
复制代码

执行结果:

Shape: Circle
Shape: Rectangle
Shape: Square
I'll draw a border in a second
复制代码

写道这里,我想起了《一代宗师》有人问叶问说:

“咏春有什么绝招啊?”

叶问答道:“就三板斧:摊、膀、伏。”

那人再问:“人家宫家六十四手千变万化,你们咏春就三板斧。摊、膀、伏,你怎么打啊?”

叶问回答道:“三板斧就够他受的了。”

coding 也是千变万化,只是GoF(Gang of Four,四人帮)对常用的一些变化进行了一些总结,形成了传说中的“剑贰拾叁式”——23种设计模式。而叶问大侠就是仅靠武术里的三招常用的招式进行组合成为了一代宗师!(如果你们谁和叶问到有切磋过,麻烦告诉我,叶问会几招几式?)所以要想成为大侠,还是得把基础的招式练好,并做到随机应变,看来我的路还比较长!

3. 高阶语法的笑话

说到笑话之前,大家先看看一开始我说的这个工具类

class PreferenceUtil<T>(val name: String, private val default: T) {


    private val prefs: SharedPreferences by lazy { BaseApplication.getInstance().getSharedPreferences(name, Context.MODE_PRIVATE) }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return getSharePreferences(name, default)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        putSharePreferences(name, value)
    }

    @SuppressLint("CommitPrefEdits")
     fun putSharePreferences(name: String, value: T) = with(prefs.edit()) {
        when (value) {
            is Long -> putLong(name, value)
            is String -> putString(name, value)
            is Int -> putInt(name, value)
            is Boolean -> putBoolean(name, value)
            is Float -> putFloat(name, value)
            else -> throw IllegalArgumentException("This type of data cannot be saved!")
        }.apply()
    }

    @Suppress("UNCHECKED_CAST")
     fun getSharePreferences(name: String, default: T): T = with(prefs) {
        val res: Any = when (default) {
            is Long -> getLong(name, default)
            // 正常情况下是不需要非空断言,但是我的 AS 就是给我穿小鞋
            is String -> getString(name, default)!!
            is Int -> getInt(name, default)
            is Boolean -> getBoolean(name, default)
            is Float -> getFloat(name, default)
            else -> throw IllegalArgumentException("This type of data cannot be saved!")
        }
        return res as T
    }
}
复制代码

在我明白上面的代理模式以后,我对着putSharePreferencesgetSharePreferences发呆,因为我完全看不懂这两个方法,后来通过面向QQ群编程以后,有人告诉我,这就是传说中的with()函数在代码里的样子。

其实我以前就知道let()函数可以在判空的时候发挥作用,run()函数在控件赋值的时候使用,如下:

// ll_rl_list 是一个RecycleView
ll_rl_list.run {
    layoutManager = LinearLayoutManager(this@PickLocationActivity)
    mAdapter.addChildClickViewIds(R.id.stv_select)
    mAdapter.setOnItemChildClickListener { _, _, position ->
        RxBus.post(mAdapter.getItem(position)!!)
        onBackPressed()
    }
    adapter = mAdapter
}

var a:String? = null
    a?.let { 
        println("$a 对象不为空,干活吧!")
    }?: println("对象为空,无法面向对象编程")
复制代码

但就算知道上面的内容,还是让我闹了一个笑话,因此我打算死磕Kotlin的几个高阶语法!

在我打开源码文件的时候,我发现TODO也在这里,真巧啊。于是我就猴子掰玉米的去看看:


public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)

@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()

@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
复制代码

以上代码不用注释都能看明白,当运行到TODO()或者TODO("**")的时候,代码会抛出一个Error,信息是An operation is not implemented.或者是**入门真是小case,接下来我们正式开始进入第一关run()函数。

3.1 run()函数

/**
 * Calls the specified function [block] and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
复制代码

他们都说源码是最好的老师,但是我TM看不懂啊!

还好我有百度,看看翻译:

调用指定的函数[block]并返回其结果。

有关详细的使用信息,请参阅[scope函数]的文档。(kotlinlang.org/docs/refere…)

查看文档:

翻译文档:

1)上下文对象作为接收器可用(this)。返回值是lambda结果。 run的作用与with相同,但调用let作为上下文对象的扩展函数。当lambda同时包含对象初始化和返回值的计算时,run非常有用。

2)除了在receiver对象上调用run外,还可以将它用作非扩展函数。非扩展运行允许您在需要表达式的地方执行多个语句块。

看到这里我差不多明白什么意思了,有两种情况:

  • receiver对象上调用run函数

直接使用文档的代码:

// 示例类
class MultiportService(var url: String, var port: Int) {
    fun prepareRequest(): String = "Default request"
    fun query(request: String): String = "Result for query '$request'"
}

// 调用
fun main() {
    val service = MultiportService("https://example.kotlinlang.org", 80)

    val result = service.run {
        port = 8080
        query(prepareRequest() + " to port $port")
    }

    println(result)
   
}
复制代码

执行结果:

Result for query 'Default request to port 8080'
复制代码
  • receiver对象调用run函数
// 调用
fun main() {
    val testValue = run {
        val a = 5
        val b = 3
        return@run 3*5
    }

    val array = intArrayOf(1,2,3,4,5)
    for (i in array) {
        println(i*testValue)
    }
   
}
复制代码

执行结果:

15
30
45
60
75
复制代码

我的结论:run函数如果有receiver,会接收一个receiver,然后返回lambda的结果;如果无receiver,则无参数,但是也会返回lambda的结果

3.2 with()函数

老规矩,上源码:

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
复制代码

文档:

翻译:

  1. 非扩展函数:上下文对象作为参数传递,但在lambda内部,它作为接收器可用(this)。返回值是lambda结果。我们建议使用with来调用上下文对象上的函数,而不提供lambda结果。在代码中,with可以读作『使用此对象,执行以下操作』。
  2. with的另一个用例是引入一个helper对象,它的属性或函数将用于计算一个值。

代码:

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    with(numbers) {
        println("'with' is called with argument $this")
        println("It contains $size elements")
    }
}
复制代码

执行结果:

'with' is called with argument [one, two, three]
It contains 3 elements
复制代码

代码:

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    val firstAndLast = with(numbers) {
        "The first element is ${first()}," +
        " the last element is ${last()}"
    }
    println(firstAndLast)
复制代码

执行结果:

The first element is one, the last element is three
复制代码

我的结论:with函数和run函数极其相似,在源码上看with函数多了一个receiver为必填参数,但是从结果上看是一样的,但是官网给的两个示例说第一种是使用receiver对象,执行操作,第二种是将receiver视作一个 『Help』 对象,利用 『Help』 的属性或者函数来返回一个值,二者的区别就是要不要有返回结果。

3.3 apply()函数

这个就有些不一样了,先看源码:

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
复制代码

上面两个函数返回的是lambda结果,但是这里返回的是作为接收的this,继续看文档:

翻译:

上下文对象作为接收器可用(this)。返回值是对象本身。

对于不返回值且主要操作receiver对象成员的代码块,请使用applyapply的常见情况是对象配置。这样的调用可以理解为『将下列赋值应用到对象』。

代码:

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    val result = numbers.apply {
        add("four")
        add("five")
    }
    println(result.toString())
    println(numbers.toString())
}
复制代码

执行结果:

[one, two, three, four, five]
[one, two, three, four, five]
复制代码

apply的常见情况是对象配置,文档里面已经翻译得十分清楚了!这里就不需要再总结了!

3.4 also()函数

also函数和apply很容易看花眼,我们看源码:

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
复制代码

注意block参数,然后我们继续看文档:

翻译:

上下文对象作为参数(it)可用。返回值是对象本身。也适用于执行一些将上下文对象作为参数的操作。还可用于不更改对象的其他操作,如日志记录或打印调试信息。通常,您可以从调用链中删除also的调用,而不会破坏程序逻辑。当您在代码中也看到它时,您可以将其理解为『并执行以下操作』。

代码:

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    numbers
        .also { println("The list elements before adding new one: $it") }
        .add("four")
    println(numbers.toString())
}
复制代码

执行结果:

The list elements before adding new one: [one, two, three]
[one, two, three, four]
复制代码

文档很给力,我们看下一个!

3.4 let()函数

先看看源码,特别提醒与run()函数的源码对比着看。

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
复制代码

你们看到最后返回的这个block了吗?区别就在于run()函数的block(),而let()返回的是block(this),看上去有点绕脑,我们去看看文档:

文档看上去比其他内容要多一些,应该也是最复杂的,我们慢慢来看一看翻译:

上下文对象作为参数(it)可用。返回值是lambda结果。

1) let可用于在调用链的结果上调用一个或多个函数。例如,下面的代码打印一个集合上两个操作的结果(代码『1』)

2) 如果代码块包含一个函数作为参数,你可以使用方法reference(::)代替lambda(示例见代码『2』)

3)let通常用于只执行非空值的代码块。若要对非空对象执行操作,请使用安全调用操作符 ? 。在其上调用let及其lambda中的动作。(这个应用最广泛的场景,示例见代码『3』)

4)使用let的另一种情况是引入局部变量,这些局部变量在改善代码可读性方面的作用有限。要为上下文对象定义一个新变量,请将其名称作为lambda参数提供,以便可以使用它而不是默认使用它。(当在一个方法内多次使用lambda语法或者let等函数的时候会经常遇到,示例见代码『4』)

代码:(翻译的内容根据使用方式分为了4部分,代码部分也是如此) 示例『1』

fun main(){
    val numbers = mutableListOf("one", "two", "three", "four", "five")
    val resultList = numbers.map { it.length }.filter { it > 3 }
    println(resultList)
    // 在调用链的结果上调用一个或多个函数
    numbers.map { it.length }.filter { it > 3 }.let {
        println(it)
        // and more function calls if needed
    }
}
复制代码

运行结果:

[5, 4, 4]
[5, 4, 4]
复制代码

示例『2』

  fun main(){
        val numbers = mutableListOf("one", "two", "three", "four", "five")
        val resultList = numbers.map { it.length }.filter { it > 3 }
        println(resultList)
        // reference(::)代替lambda
        numbers.map { it.length }.filter { it > 3 }.let(::println)
    }

复制代码

运行结果:(和示例『1』一样的动作,故结果也是一样)

[5, 4, 4]
[5, 4, 4]
复制代码

示例『3』

var a:String? = null
fun main(){
    
    a?.let { 
        println("$a 对象不为空,干活吧!")
    }?: println("对象为空,无法面向对象编程")
}


复制代码

运行结果:

对象为空,无法面向对象编程
复制代码

示例『4』——引入局部变量,改善代码可读性


val list:MutableList<List<String>?>? = mutableListOf()
fun main(){
    val numbers = listOf("one", "two", "three", "four")
    list?.add(numbers)
    val modifiedFirstItem = list?.let {
        it.first()?.let { list ->
            list.first().let { firstItem ->
                println("The first item of the list is '$firstItem'")
                if (firstItem.length >= 5) firstItem else "!$firstItem!"
            }.toUpperCase()
        }
    }
    println("First item after modifications: '$modifiedFirstItem'")
}
复制代码

执行结果:

The first item of the list is 'one'
First item after modifications: '!ONE!'
复制代码

我的结论:let函数除了示例『1』和示例『3』,其它两种用得比较多,而第一和第三平时也比较使用。文档还是很给力。

3.5 其它函数

这里讲其它函数,是因为这些函数相对而言,使用频率不是特别高,我们可以作为一个了解即可。

3.5.1 Contract()函数

话说我看见上面的run()函数源码时,一下就栽在这个Contract()函数里面了,仔细看了半天,完全不知道这个契约函数究竟是解决什么问题的?后来经过在网上查找资源,总算知道一个大概了,下面的内容来自《Kotlin Contract 契约》极简教程,详情可以点击链接查看原文。

先看一些简单的,大家能答得上来的

val nullList: List<Any>? = null
val b1 = nullList.isNullOrEmpty() // true

val empty: List<Any>? = emptyList<Any>()
val b2 = empty.isNullOrEmpty() // true

val collection: List<Char>? = listOf('a', 'b', 'c')
val b3 = collection.isNullOrEmpty() // false

fun f1(s: String?): Int {
        return if (s != null) {
            s.length
        } else 0
    }
复制代码

这个错误呢?知道为什么吗?

fun f2(s: String?): Int {
    return if (isNotEmpty(s)) {
        s?.length ?: 0  // 我们需要使用 ?.
    } else 0
}

fun isNotEmpty(s: String?): Boolean {
    return s != null && s != ""
}
复制代码

WHY ?

Contract 契约就是来解决这个问题的.

我们都知道Kotlin中有个非常nice的功能就是类型智能推导(官方称为smart cast),不知道小伙伴们在使用Kotlin开发的过程中有没有遇到过这样的场景,会发现有时候智能推导能够正确识别出来,有时候却失败了。

更多信息请点击上面链接《Kotlin Contract 契约》极简教程查看原文吧!

3.5.2 takeIf()函数

这个就简单了,先看源码:

/**
 * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}
复制代码

这里没有文档,我们直接看注释:

Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't
如果满足给定的[predicate],则返回上下文的'this';如果不满足给定的[predicate],则返回' null '。
复制代码

看完后脑子里突然蹦出一个fliter的函数,在集合里可以通过这个函数来筛选符合预期的元素;而这里是对象对自身进行的一个条件判断,满足则返回这个对象,不满足则返回null。我们看看示例:

fun main(){
    val students = intArrayOf(99,100,72,48,61)
    val result = students.map {
        val gradle = it.takeIf { grade ->
            return@takeIf grade>= 60
        }
        return@map if(gradle != null) "及格" else "不及格"
    }
    println(result.toString())
}
复制代码

执行结果:

[及格, 及格, 及格, 不及格, 及格]
复制代码

3.5.3 takeUnless()函数

这个函数和takeIf()函数是一对的,还是先看源码:

/**
 * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}
复制代码

翻译注释:

Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
如果不满足给定的[谓词],则返回' this '值;如果满足给定的[谓词],则返回' null '复制代码

这个比较简单了,和takeIf()函数是相反的,因此我们直接上示例代码:

fun main(){
    val students = intArrayOf(99,100,72,48,61)
    val result = students.map {
        val gradle = it.takeIf { grade ->
            // 请特别注意这里的条件判断
            return@takeIf grade < 60
        }
        return@map if(gradle != null) "及格" else "不及格"
    }
    println(result.toString())
}
复制代码

执行结果:

[及格, 及格, 及格, 不及格, 及格]
复制代码

3.5.4 repeat()函数

如果上面完全看明白了,这部分应该完全不是问题,我们看源码:

/**
 * Executes the given function [action] specified number of [times].
 *
 * A zero-based index of current iteration is passed as a parameter to [action].
 *
 * @sample samples.misc.ControlFlow.repeat
 */
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}
复制代码

翻译注释:

Executes the given function [action] specified number of [times].
执行给定的函数[操作]指定的次数[次]。
A zero-based index of current iteration is passed as a parameter to [action].
当前迭代的从零开始的索引作为参数传递给[action]。
复制代码

这个比较简单,我们看示例和运算结果:

fun main(){
    repeat(10){
        println("我是重复的第${it + 1}次,我的索引为:$it")
    }
}
复制代码

计算结果:

我是重复的第1次,我的索引为:0
我是重复的第2次,我的索引为:1
我是重复的第3次,我的索引为:2
我是重复的第4次,我的索引为:3
我是重复的第5次,我的索引为:4
我是重复的第6次,我的索引为:5
我是重复的第7次,我的索引为:6
我是重复的第8次,我的索引为:7
我是重复的第9次,我的索引为:8
我是重复的第10次,我的索引为:9
复制代码

这个方法需要注意的是,这个重复执行没有设置时间间隔,相当于是连续重复执行n次,如果需要时间间隔,比如每分钟执行一次,需要选择其它方法。

函数选择

看了这么多函数,最后改如何选择呢?文档里面已经给出方案了

为了帮助您选择合适的范围函数,我们提供了它们之间的主要区别表。

功能 对象参考 返回值 是扩展功能
let it Lambda结果
run this Lambda结果
run -- Lambda结果 否:在没有上下文对象的情况下调用
with this Lambda结果 否:将上下文对象作为参数。
apply this 上下文对象
also it 上下文对象

如果看不懂也没有关系,还有根据预期目的选择范围函数的简短指南:

  • 在非null对象上执行lambdalet

  • 将表达式引入为局部作用域中的变量: let

  • 对象配置: apply

  • 对象配置和计算结果: run

  • 需要表达式的运行语句:非扩展 run

  • 附加效果: also

  • 对对象进行分组功能调用: with

  • 不同功能的用例重叠,因此您可以根据项目或团队中使用的特定约定选择功能。

尽管范围函数是使代码更简洁的一种方法,但请避免过度使用它们:这会降低代码的可读性并导致错误。避免嵌套作用域函数,并在链接它们时要小心:容易对当前上下文对象和thisor 的值感到困惑it

强烈建议大家都来看看这篇官网文档(不用翻墙):Scope Functions,感觉写得真的很好。如果英文不是很好的同学,也可以像我一样使用翻译功能来进行阅读学习!

4. EasySharedPreferences

你以为这样就完了吗?不,当然是把好工具分享给大家啊!但是有朋友可能会问,上面不是已经有源码了,你还分享个啥?理由有如下两点:

  • 如果我们存储的是一个常量,而非一个对象,使用这个工具类取值一行代码已经足够方便了,但是取值的时候其实也可以支持一行代码即可,增加一个静态工具类即可。(其实之前我用EasySharedPreferences也不多,因为主要用来存储一些常量的时候不方便,现在好了,短板已经补上了!)
  • 如果我们存储的是一个对象,那么我们可以使用直接使用EasySharedPreferences,它有以下三个优点:
    1. 支持存储任意数据
    2. 缓存加速
    3. 自动同步
    4. 更多信息,请查询优雅的进行SharedPreferences数据存储操作

上面提到的一行代码实现通过SharedPreferences实现存取值:

class EasySharedPreferences{
    companion object{
        @JvmStatic
        fun <E>getSPValue(key: String, default: E):E{
            val result:E by PreferenceUtil(key, default)
            return result
        }

        @JvmStatic
        fun <E>putSPValue(key: String, value: E){
            var old:E by PreferenceUtil(key, value)
            old = value
        }
    }
}

// 调用示例
// put
EasySharedPreferences.putSPValue("key","value")
// get
EasySharedPreferences.getSPValue("key","default")
复制代码

上文如有错误,请各位批评指正,我们一起成长,谢谢!

源码:传送门

参考资料:

kotlin委托

官网文档:Scope Functions(不用翻墙的)

《Kotlin Contract 契约》极简教程