聊一聊Kotlin之contract

1,306 阅读2分钟

我正在参加「掘金·启航计划」

Kotlin是由JetBrains开发的针对JVM、Android和浏览器的静态编程语言,是Android的官方语言。Kotlin拥有较多高级而又简洁的语法特性,提升了我们的开发效率,减少了代码量。

在开始之前先看一块代码段:

fun printLength(str: String? = null) {
    println(str?.length) // 1

    if (!TextUtils.isEmpty(str)) { // 2
        println(str?.length) 
    }

    if (!str.isNullOrEmpty()) {  // 3
        println(str.length)
    }
}

为何1和2两处需要添加问号?,而3处则不需要,为什么呢?直接见源码,这里的isNullOrEmpty其实是Kotlin中Strings.kt的扩展方法:

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }
​
    return this == null || this.length == 0
}

你会发现里面应用了contract,我们不妨照着用contract写一个:

@ExperimentalContracts
inline fun String?.isNotNullWithContract(): Boolean {
    contract {
        returns(true) implies (this@isNotNullWithContract != null)
    }
​
    return this != null && this.isNotEmpty()
}

那我们使用一下看看:

class CheckNullTest {
    @ExperimentalContracts
    inline fun String?.isNotNullWithContract(): Boolean {
        contract {
            returns(true) implies (this@isNotNullWithContract != null)
        }
​
        return this != null && this.isNotEmpty()
    }
​
    @ExperimentalContracts
    fun printLength(str: String? = null) {
        println(str?.length) // 1
​
        if (!TextUtils.isEmpty(str)) { // 2
            println(str?.length)
        }
​
        if (!str.isNullOrEmpty()) {  // 3
            println(str.length)
        }
​
        if (str.isNotNullWithContract()) {  // 4
            println(str?.length)
        }
    }
}

你会发现4处还是需要用问号?,怎么仿着写无效,contract没有生效?看contract源码:

/**
 * Specifies the contract of a function.
 *
 * The contract description must be at the beginning of a function and have at least one effect.
 *
 * Only the top-level functions can have a contract for now.
 *
 * @param builder the lambda where the contract of a function is described with the help of the [ContractBuilder] members.
 *
 */
@ContractsDsl
@ExperimentalContracts
@InlineOnly
@SinceKotlin("1.3")
@Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }
​

见注释中Contracts are allowed only for top-level functions, 原来是使用的姿势有问题,那换个姿势再来一次:

@ExperimentalContracts
inline fun String?.isNotNullWithContract(): Boolean {
    contract {
        returns(true) implies (this@isNotNullWithContract != null)
    }
​
    return this != null && this.isNotEmpty()
}
​
class CheckNullTest {
​
    @ExperimentalContracts
    fun printLength(str: String? = null) {
        println(str?.length) // 1
​
        if (!TextUtils.isEmpty(str)) { // 2
            println(str?.length)
        }
​
        if (!str.isNullOrEmpty()) {  // 3
            println(str.length)
        }
​
        if (str.isNotNullWithContract()) {  // 4
            println(str.length)
        }
    }
}

此时发现终于和Kotlin中Strings.kt的扩展方法isNullOrEmpty()表现一致了,不用添加问号?了。

contract(契约)是一种 Kotlin 面向编译器约定的一种规则,它帮助编译器更加智能地识别某些需要特定的代码条件,为代码创建更加友好的上下文关联环境。 Kotlin 在 1.3 版本以实验室功能的方式开始引入 contract, 截止至当前 Kotlin 最新版本 1.6.10,contract 方法依然添加有 @ExperimentalContracts 注解。

常见标准库函数run,also,with,apply,let,每个里面都用到contract契约:

//以apply函数举例,其他函数同理
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}