Kotlin Contract API 详解

Kotlin Contract API 详解

Kotlin Contract API 是一种强大的工具,它允许开发者通过特定的契约声明来增强编译器的智能推断能力。这种能力不仅可以提高代码的安全性,还能优化代码的执行效率。以下是对 Kotlin Contract API 的详细介绍:

智能推断与 Contract

Kotlin 语言以其智能类型推断而闻名,但在某些情况下,这种推断能力可能会受到限制。例如,当处理可空类型时,编译器可能无法自动推断出类型。以下是一个示例:

fun String?.isNotNull():Boolean {
    return this!=null && this.isNotEmpty()
}

fun printLength(s:String?=null) {
    if (!s.isNotNull()) {
        println(s.length) // 编译错误:不允许在可空类型上调用 length
    }
}

在这个例子中,编译器无法推断 s 为非空类型,因此无法编译通过。

然而,使用 Kotlin 标准库中的 isNullOrEmpty() 函数,可以解决这个问题:

fun printLength(s:String?=null) {
    if (!s.isNullOrEmpty()) {
        println(s.length)
    }
}

isNullOrEmpty() 函数通过 Contract 声明告诉编译器,当它返回 false 时,字符串 s 不为 null。这样,编译器就可以安全地推断 s 为非空类型。

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

    return this == null || this.length == 0
}

Contract 的特性

Contract 声明是一种特殊的编程构造,它允许开发者向编译器提供额外的信息。以下是一些关键特性:

  • Contract 只能在顶级函数体内使用,不能在成员函数或类函数中使用。

  • Contract 声明必须是函数体内的第一条语句。

  • 编译器目前不会验证 Contract 的正确性,因此开发者需要确保 Contract 的准确性。

Kotlin 1.4 中的改进

在 Kotlin 1.4 中,Contract API 进行了一些重要的改进:

  • 支持在内联函数中使用 Contract。

  • 允许为 final 类型的成员函数添加 Contract。

Contract 的类型

Contract 主要有两种类型:Returns Contracts 和 CallInPlace Contracts。

Returns Contracts

Returns Contracts 描述了函数返回特定值时的附加条件。例如:

  • returns(true) implies:当函数返回 true 时,附加条件成立。

  • returns(false) implies:当函数返回 false 时,附加条件成立。

  • returns(null) implies:当函数返回 null 时,附加条件成立。

  • returns implies:当函数正常返回时,附加条件成立。

  • returnsNotNull implies:当函数返回非 null 值时,附加条件成立。

以下是 requireNotNull() 函数的示例:

@kotlin.internal.InlineOnly
public inline fun requireNotNull(value: T?): T {
    contract {
        returns() implies (value != null)
    }
    return requireNotNull(value) { "Required value was null." }
}

这个 Contract 声明告诉编译器,如果 requireNotNull 函数正常返回,则 value 不为 null

CallInPlace Contracts

CallInPlace Contracts 描述了函数参数的调用次数和调用位置。例如:

@kotlin.internal.InlineOnly
public inline fun T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

这个 Contract 声明告诉编译器,lambda 表达式 blocklet 函数中只会被调用一次。

Contract 源码解析

Contract 使用 DSL(领域特定语言)方式进行声明。以下是 contract() 函数的源码:

@ContractsDsl
@ExperimentalContracts
@InlineOnly
@SinceKotlin("1.3")
@Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }

ContractBuilder 接口提供了多种方法来描述 Contract,如 returns()returnsNotNull()callsInPlace()

ContractBuilder 构建了 Contract,其源码如下:

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface ContractBuilder {
    /**
     * Describes a situation when a function returns normally, without any exceptions thrown.
     *
     * Use [SimpleEffect.implies] function to describe a conditional effect that happens in such case.
     *
     */
    // @sample samples.contracts.returnsContract
    @ContractsDsl public fun returns(): Returns

    /**
     * Describes a situation when a function returns normally with the specified return [value].
     *
     * The possible values of [value] are limited to `true`, `false` or `null`.
     *
     * Use [SimpleEffect.implies] function to describe a conditional effect that happens in such case.
     *
     */
    // @sample samples.contracts.returnsTrueContract
    // @sample samples.contracts.returnsFalseContract
    // @sample samples.contracts.returnsNullContract
    @ContractsDsl public fun returns(value: Any?): Returns

    /**
     * Describes a situation when a function returns normally with any value that is not `null`.
     *
     * Use [SimpleEffect.implies] function to describe a conditional effect that happens in such case.
     *
     */
    // @sample samples.contracts.returnsNotNullContract
    @ContractsDsl public fun returnsNotNull(): ReturnsNotNull

    /**
     * Specifies that the function parameter [lambda] is invoked in place.
     *
     * This contract specifies that:
     * 1. the function [lambda] can only be invoked during the call of the owner function,
     *  and it won't be invoked after that owner function call is completed;
     * 2. _(optionally)_ the function [lambda] is invoked the amount of times specified by the [kind] parameter,
     *  see the [InvocationKind] enum for possible values.
     *
     * A function declaring the `callsInPlace` effect must be _inline_.
     *
     */
    /* @sample samples.contracts.callsInPlaceAtMostOnceContract
    * @sample samples.contracts.callsInPlaceAtLeastOnceContract
    * @sample samples.contracts.callsInPlaceExactlyOnceContract
    * @sample samples.contracts.callsInPlaceUnknownContract
    */
    @ContractsDsl public fun  callsInPlace(lambda: Function, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace

Effect 接口

目前 Kotlin 只支持有 4 种 Effect:

  • Returns:表示函数成功返回,不会不引发异常。

  • ReturnsNotNull:表示函数成功返回不为 null 的值。

  • ConditionalEffect:表示一个效果和一个布尔表达式的组合,如果触发了效果,则保证为true。

  • CallsInPlace:表示对传递的 lambda 参数的调用位置和调用次数的约束。

所有 Contract 效果(如 ReturnsReturnsNotNullConditionalEffectCallsInPlace)都实现了 Effect 接口:

@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface Effect

总结

使用 Contract 可以为编译器提供更多线索,从而提高代码的安全性和智能推断的效果。然而,开发者需要自己确保 Contract 的正确性。未来,Kotlin 可能会为 Contract 提供更完善的语法检查和支持。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值