前置知识: Kotlin

Kotlin契约

00:00
4 min Advanced 2026/6/14

契约与编译器提示

概述

Kotlin 契约(Contract)是一种向编译器提供额外类型信息的机制。通常,编译器只能根据代码的静态结构做类型推断,但有些情况下,程序员知道更多的运行时信息。契约让你告诉编译器:“如果这个函数正常返回,那么某个条件一定成立”,从而帮助编译器做更精确的类型推断,减少不必要的类型转换或空检查。

最常见的例子是 requireNotNull 函数:调用它之后,编译器就知道参数不为 null 了,不需要再手动做空检查。

基础概念

  • contract 块:用 contract { } 声明契约,放在函数体的最前面
  • returns() implies:最常用的契约形式,表示”如果函数正常返回,则某个条件成立”
  • callsInPlace:表示传入的 lambda 会在特定时机被调用,用于智能推断局部变量的初始化状态
  • Experimental:契约目前仍是实验性特性,需要添加 @OptIn(ExperimentalContracts::class) 注解

快速上手

import kotlin.contracts.*

// 最简单的契约:告诉编译器,如果函数正常返回,则 value 不为 null
@OptIn(ExperimentalContracts::class)
fun requireNonNull(value: Any?) {
    contract { returns() implies (value != null) }
    if (value == null) throw IllegalArgumentException("值不能为空")
}

fun process(s: String?) {
    requireNonNull(s)
    // 没有契约时,编译器不知道 s 不为 null,需要 s!!.length
    // 有了契约,编译器知道 s 不为 null
    println(s.length)  // 直接使用,无需 !! 或 ?.
}

详细用法

returns() implies 契约

这是最常用的契约形式,表示”函数正常返回意味着某个条件成立”:

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 契约:如果函数返回 true,则 value 不为 null
fun isValid(value: String?): Boolean {
    contract { returns(true) implies (value != null) }
    return value != null && value.isNotEmpty()
}

fun main() {
    val input: String? = readLine()
    if (isValid(input)) {
        // 编译器知道 input 不为 null,因为 isValid 返回 true
        println("输入长度: ${input.length}")
    }
}

returns(false) implies 契约

也可以在返回 false 时提供信息:

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 如果函数返回 false,则 value 为 null
fun isNullOrEmpty(value: String?): Boolean {
    contract { returns(false) implies (value != null) }
    return value == null || value.isEmpty()
}

fun main() {
    val input: String? = readLine()
    if (!isNullOrEmpty(input)) {
        // 编译器知道 input 不为 null
        println("非空输入: ${input.length}")
    }
}

callsInPlace 契约

callsInPlace 告诉编译器传入的 lambda 会被调用几次、在什么时候调用

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// EXACTLY_ONCE:lambda 恰好被调用一次
fun <T> runOnce(block: () -> T): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    return block()
}

@OptIn(ExperimentalContracts::class)
// AT_LEAST_ONCE:lambda 至少被调用一次
fun <T> runAtLeastOnce(block: () -> T): T {
    contract { callsInPlace(block, InvocationKind.AT_LEAST_ONCE) }
    return block()
}

@OptIn(ExperimentalContracts::class)
// AT_MOST_ONCE:lambda 最多被调用一次
fun <T> runAtMostOnce(condition: Boolean, block: () -> T): T? {
    contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
    return if (condition) block() else null
}

fun main() {
    // 没有 callsInPlace 契约时,编译器不知道 lambda 一定会执行
    // 所以 x 可能未初始化
    val x: Int
    runOnce { x = 42 }  // 有契约,编译器知道 x 一定会被赋值
    println(x)  // 可以直接使用

    val y: Int
    runAtLeastOnce { y = 100 }
    println(y)  // 可以直接使用
}

自定义条件契约

可以创建更复杂的条件判断函数

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 判断是否为特定类型
fun <T> isType(value: Any?, klass: Class<T>): Boolean {
    contract { returns(true) implies (value != null) }
    return klass.isInstance(value)
}

// 更实用的例子:验证列表非空
@OptIn(ExperimentalContracts::class)
fun <T> isNotEmpty(list: List<T>?): Boolean {
    contract { returns(true) implies (list != null) }
    return list != null && list.isNotEmpty()
}

fun main() {
    val items: List<String>? = listOf("a", "b", "c")
    if (isNotEmpty(items)) {
        // 编译器知道 items 不为 null
        println("第一个元素: ${items.first()}")
    }
}

结合扩展函数的契约

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 扩展函数中的契约
fun String?.isNotBlankOrThrow(): String {
    contract { returns() implies (this@isNotBlankOrThrow != null) }
    if (this == null || this.isBlank()) {
        throw IllegalArgumentException("字符串为空或空白")
    }
    return this
}

fun main() {
    val text: String? = "  Hello  "
    val trimmed = text.trim()
    trimmed.isNotBlankOrThrow()
    // 编译器知道 trimmed 不为 null
    println(trimmed.length)
}

常见场景

封装常见的空检查模式

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 如果 value 为 null,执行默认操作
inline fun <T : Any> T?.ifNull(action: () -> T): T {
    contract { returns() implies (this@ifNull != null) }
    return this ?: action()
}

@OptIn(ExperimentalContracts::class)
// 确保条件成立,否则抛出异常
fun ensure(condition: Boolean, lazyMessage: () -> String) {
    contract { returns() implies condition }
    if (!condition) throw IllegalStateException(lazyMessage())
}

fun main() {
    val name: String? = getInput()
    name.ifNull { "默认值" }
    // 编译器知道 name 不为 null
    println(name.length)

    val age = -1
    ensure(age >= 0) { "年龄不能为负数" }
    // 编译器知道 age >= 0
}

简化类型检查

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 类型检查并智能转换
inline fun <reified T> Any?.isInstanceOf(): Boolean {
    contract { returns(true) implies (this@isInstanceOf is T) }
    return this is T
}

fun main() {
    val value: Any = "Hello"
    if (value.isInstanceOf<String>()) {
        // 编译器知道 value 是 String
        println(value.length)
    }
}

状态验证

import kotlin.contracts.*

class StateMachine {
    private var initialized = false
    private var data: String? = null

    @OptIn(ExperimentalContracts::class)
    fun requireInitialized() {
        contract { returns() implies (this@StateMachine.initialized) }
        check(initialized) { "状态机未初始化" }
    }

    fun initialize(input: String) {
        data = input
        initialized = true
    }

    fun process() {
        requireInitialized()
        // 编译器知道 initialized 为 true
        // 但 data 仍需要空检查,因为契约只能作用于条件表达式
        println(data?.length)
    }
}

注意事项

  • 实验性特性契约目前仍是实验性功能,API 可能会变化,需要用 @OptIn(ExperimentalContracts::class) 标注
  • 契约必须在函数体第一条语句contract { } 必须是函数体的第一条语句,之前不能有任何代码
  • 只能用在内联函数或有固定实现的函数契约不能用在 open 或 abstract 函数
  • 编译器不会验证契约的正确性:如果你声明错误契约(比如函数可能返回 null契约说不会),编译器不会报错,但可能导致运行时错误
  • 不要滥用契约:只在确实能帮助编译器做更精确推断时使用,不要为了省略空检查而随意添加契约
  • 契约影响编译期推断契约不会改变运行时行为,只是帮助编译器做更精确的类型推断

进阶用法

组合多个条件

import kotlin.contracts.*

@OptIn(ExperimentalContracts::class)
// 同时检查多个条件
fun <A, B> requireBothNotNull(a: A?, b: B?) {
    contract {
        returns() implies (a != null)
        returns() implies (b != null)
    }
    requireNotNull(a) { "第一个参数不能为空" }
    requireNotNull(b) { "第二个参数不能为空" }
}

fun main() {
    val x: String? = "Hello"
    val y: Int? = 42
    requireBothNotNull(x, y)
    // 编译器知道 x 和 y 都不为 null
    println(x.length + y)
}

标准库中的契约

Kotlin 标准库中很函数已经使用了契约,了解它们可以帮你更好地利用编译器智能推断:

// 标准库中的契约示例(不需要自己添加 @OptIn)

// require - 如果返回,则条件为 true
require(x > 0)
// 此后编译器知道 x > 0

// check - 如果返回,则条件为 true
check(state != null)
// 此后编译器知道 state 不为 null

// let - callsInPlace 契约
val result = value?.let {
    // it 一定不为 null
    it.length
}

// also - callsInPlace 契约
val configured = list.also {
    // it 一定不为 null
    it.add("item")
}

// run/with/apply - 都有 callsInPlace 契约
val x: Int
run { x = 42 }
println(x)  // 编译器知道 x 已初始化

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式