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 表达式 block
在 let
函数中只会被调用一次。
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 效果(如 Returns
、ReturnsNotNull
、ConditionalEffect
、CallsInPlace
)都实现了 Effect
接口:
@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface Effect
总结
使用 Contract 可以为编译器提供更多线索,从而提高代码的安全性和智能推断的效果。然而,开发者需要自己确保 Contract 的正确性。未来,Kotlin 可能会为 Contract 提供更完善的语法检查和支持。