如何让Kotlin识别参数非空时函数返回值非空?Map扩展函数契约问题
解答:Kotlin契约完全支持扩展函数,这么做就对了!
嘿,这个问题我之前折腾过,刚好能给你捋清楚~首先明确说:Kotlin的契约机制是支持扩展函数的,你之前用Java的@Contract注解没用是因为Kotlin编译器对Kotlin代码的空性推断靠的是自己的契约DSL,而非Java的注解。下面给你两种实用解决方案:
方案一:用Kotlin契约实现空性推断
Kotlin的契约需要用到kotlin.contracts包里的API,目前这个API还是实验性的,所以需要先开启相关配置。
步骤1:给扩展函数添加契约声明
在你的Map扩展函数最开头加上契约块,明确告诉编译器:当默认值default非空时,函数返回值一定非空。示例代码如下:
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract // 标记使用实验性契约API @ExperimentalContracts fun <T> Map<String, *>.getAndParse(key: String, default: T, parser: (Any?) -> T): T { // 契约块必须放在函数体最开头 contract { // 声明:如果函数返回的是传入的default,那么default一定非空 returns(default) implies (default != null) // 补充规则:只要default非空,函数返回值就不会为空 if (default != null) { returnsNotNull() } } return try { parser(get(key)) } catch (e: Exception) { default } }
步骤2:启用实验性API
如果你用Gradle构建项目,需要在build.gradle.kts里添加配置,允许使用实验性契约:
tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs += "-Xexperimental=kotlin.contracts.ExperimentalContracts" } }
配置完成后,当你传入非空默认值时,编译器会自动推断返回值非空,比如:
val myMap = mapOf("age" to "25") // 编译器自动识别ageValue是非空Int,不需要!!断言 val ageValue: Int = myMap.getAndParse("age", 0) { it.toString().toInt() }
方案二:用函数重载替代契约(无实验性API依赖)
如果你不想依赖实验性API,还有更稳妥的方法:通过重载函数,分开处理非空默认值和可空默认值的场景,让编译器通过函数签名自动推断空性:
// 处理非空默认值的版本,返回非空类型T fun <T : Any> Map<String, *>.getAndParse(key: String, default: T, parser: (Any?) -> T): T { return try { parser(get(key)) ?: default } catch (e: Exception) { default } } // 处理可空默认值的版本,返回可空类型T? fun <T> Map<String, *>.getAndParse(key: String, default: T?, parser: (Any?) -> T?): T? { return try { parser(get(key)) ?: default } catch (e: Exception) { default } }
这个方案不需要任何实验性依赖,编译器能完美识别:调用第一个重载(传入非空default)时返回值必然非空;调用第二个重载时返回值为可空类型。
常见误区说明
你之前用Java的@Contract注解无效,是因为Kotlin编译器对Kotlin代码的空性推断逻辑和Java不同——Java的注解只对Java代码生效,Kotlin代码需要用自身的契约DSL来声明行为。另外,Kotlin契约不仅支持扩展函数,普通函数、高阶函数等也都适用,只要按照规范写契约块就行。
内容的提问来源于stack exchange,提问作者Grzegorz Adam Hankiewicz
相关产品推荐
相关产品推荐

