Kotlin Contract
Kotlin 的智能推断是其语言的一大特色。
智能推断,能够根据类型检测自动转换类型。
但是,智能推断并没有想象中的强大,例如下面的代码就无法进行推断,导致编译失败:
fun String?.isNotNull():Boolean {return this!=null && this.isNotEmpty()
}fun printLength(s:String?=null) {if (!s.isNotNull()) {println(s.length) // Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?}}
因为编译器在处理s.length
时,会将 s 推断成value-parameter s: String? = ...
并不是 String 类型。智能推断失效了,代码也无法编译。
对上述代码做如下修改,即可编译成功:
fun printLength(s:String?=null) {if (!s.isNullOrEmpty()) {println(s.length)}
}
isNullOrEmpty() 是 Kotlin 标准库中 String 的扩展函数,其源码:
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {contract {returns(false) implies (this@isNullOrEmpty != null)}return this == null || this.length == 0
}
我们会发现 isNullOrEmpty() 的源码中包含了contract
函数,实际上它会告诉编译器当 isNullOrEmpty() 返回 false 时,则 isNullOrEmpty != null 成立,因此 printLength() 函数中的变量 s 不会为 null。
通过契约,开发者可以向编译器提供有关函数的行为,以帮助编译器对代码执行更完整的分析。
契约就像是开发者和编译器沟通的桥梁,但是编译器必须无条件地遵守契约。
一. Contract 的概念
Contract 是一种向编译器通知函数行为的方法。
Contract 是 Kotlin1.3 的新特性,在当前 Kotlin 1.4 时仍处于试验阶段。
二. Contract 的特性
只能在 top-level 函数体内使用 Contract,不能在成员和类函数上使用它们。
Contract 所调用的声明必须是函数体内第一条语句。
目前 Kotlin 编译器并不会验证 Contract,因此开发者有责任编写正确合理的 Contract。
在 Kotlin 1.4 中,对于 Contract 有两项改进:
支持使用内联特化的函数来实现契约
Kotlin 1.3 不能为成员函数添加 Contract,从 Kotlin 1.4 开始支持为 final 类型的成员函数添加 Contract(当然任意成员函数可能存在被覆写的问题,因而不能添加)。
当前 Contract 有两种类型:
Returns Contracts
CallInPlace Contracts
2.1 Returns Contracts
Returns Contracts 表示当 return 的返回值是某个值(例如true、false、null)时,implies
后面的条件成立。
Returns Contracts 有以下几种形式:
returns(true) implies
returns(false) implies
returns(null) implies
returns implies
returnsNotNull implies
其他几个类型按照字面意思很好理解,returns implies 怎么理解呢?
我们来看一下 Kotlin 的 requireNotNull() 函数的源码:
@kotlin.internal.InlineOnly
public inline fun <T : Any> requireNotNull(value: T?): T {contract {returns() implies (value != null)}return requireNotNull(value) { "Required value was null." }
}@kotlin.internal.InlineOnly
public inline fun <T : Any> requireNotNull(value: T?, lazyMessage: () -> Any): T {contract {returns() implies (value != null)}if (value == null) {val message = lazyMessage()throw IllegalArgumentException(message.toString())} else {return value}
}
contract() 告诉编译器,如果调用 requireNotNull 函数后能够正常返回,且没有抛出异常,则 value 不为空。
因此,returns implies 表示当该函数正常返回时,implies
后面的条件成立。
Contract 正是通过这种声明函数调用的结果与所传参数值之间的关系来改进 Kotlin 智能推断的效果。
2.2 CallInPlace Contracts
前面Kotlin 如何优雅地使用 Scope Functions曾介绍过 Scope Function,我们来回顾一下 let 函数的源码:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}return block(this)
}
contract() 中的 callsInPlace 会告知编译器,lambda 表达式 block 在 let 函数内只会执行一次。在 let 函数被调用结束后,block 将不再被执行。
callsInPlace() 允许开发者提供对调用的 lambda 表达式进行时间/位置/频率上的约束。
callsInPlace() 中的 InvocationKind 是一个枚举类,包含如下的枚举值:
AT_MOST_ONCE:函数参数将被调用一次或根本不调用。
EXACTLY_ONCE:函数参数将只被调用一次。
AT_LEAST_ONCE:函数参数将被调用一次或多次。
UNKNOWN:一个函数参数它可以被调用的次数未知。
Kotlin 的 Scope Function 都使用了上述 Contracts。
三. Contract 源码解析
Contract 采用 DSL 方式进行声明,我们来看一下 contract() 函数的源码:
@ContractsDsl
@ExperimentalContracts
@InlineOnly
@SinceKotlin("1.3")
@Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }
通过 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 <R> callsInPlace(lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace
}
returns()、returnsNotNull()、callsInPlace() 分别返回 Returns、ReturnsNotNull、CallsInPlace 对象。这些对象最终都实现了 Effect 接口:
@ContractsDsl
@ExperimentalContracts
@SinceKotlin("1.3")
public interface Effect
Effect 表示函数调用的效果。每当调用一个函数时,它的所有效果都会被激发。编译器将收集所有激发的效果以便于其分析。
目前 Kotlin 只支持有 4 种 Effect:
Returns:表示函数成功返回,不会不引发异常。
ReturnsNotNull:表示函数成功返回不为 null 的值。
ConditionalEffect:表示一个效果和一个布尔表达式的组合,如果触发了效果,则保证为true。
CallsInPlace:表示对传递的 lambda 参数的调用位置和调用次数的约束。
四. 小结
Contract 是帮助编译器分析的一个很好的工具,它们对于编写更干净、更好的代码非常有帮助。在使用 Contract 的时候,请不要忘记编译器不会去验证 Contract。
关注【Java与Android技术栈】
更多精彩内容请关注扫码:
Kotlin Contract相关推荐
- Kotlin contract 用法及原理
什么是 contract contract(契约)是一种 Kotlin 面向编译器约定的一种规则,它帮助编译器更加智能地识别某些需要特定的代码条件,为代码创建更加友好的上下文关联环境. Kotlin ...
- Kotlin Contract(契约)
虽然kotlin有智能推断功能,但是有时候还是有些代码没有办法进行推断,因此,诞生了Contract(契约)功能,不过目前该功能还是实验功能(ExperimentalContracts),其使用可以参 ...
- 探索 Kotlin 协程原理
接下来跟大家分享一下我在了解 Kotlin 协程实现的过程中理解的一些概念,如果你发现哪些地方我说错了的话,欢迎提出你的理解. 1. Kotlin 协程原理概述 Kotlin 协程的大致的执行流程如上 ...
- java面试题_阿里大厂流出的数百道 Java 经典面试题
BAT 常问的 Java基础39道常见面试题 1.八种基本数据类型的大小,以及他们的封装类 2.引用数据类型 3.Switch能否用string做参数 4.equals与==的区别 5.自动装箱,常量 ...
- Kotlin 标准库随处可见的 contract 到底是什么?
Kotlin 的标准库提供了不少方便的实用工具函数,比如 with, let, apply 之流,这些工具函数有一个共同特征:都调用了 contract() 函数. @kotlin.internal. ...
- Kotlin let、with、run、apply、also函数的使用
let,with,run,apply,also 是内联扩展函数 下面是自己使用的心的如果有错的地方希望给予指正谢谢 这几个主要用来简化操作,使得代码可读性提高 ,下面列举项目中使用效果 1 let 先 ...
- 【Kotlin】apply 内联扩展函数 ( apply 函数原型 | apply 函数示例 | Kotlin 调用 Java API )
文章目录 I . 内联扩展函数 apply II . Kotlin 调用 Java API III . apply 内联扩展函数示例 ( 调用 Java API 处理图像 ) I . 内联扩展函数 a ...
- [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)
简述: 不知道是否有小伙伴还记得我们之前的Effective Kotlin翻译系列,之前一直忙于赶时髦研究Kotlin 1.3中的新特性.把此系列耽搁了,赶完时髦了还是得踏实探究本质和基础,从今天开始 ...
- Kotlin极简教程:第4章 基本数据类型与类型系统
原文链接:https://github.com/EasyKotlin 到目前为止,我们已经了解了Kotlin的基本符号以及基础语法.我们可以看出,使用Kotlin写的代码更简洁.可读性更好.更富有生产 ...
最新文章
- [更新问题]无法在安装新的版本前,为“./boot/vmlinuz-2.6.24-19-generic”做一个符号链接备份...
- [Contest20171109]函数(lipshitz)
- linux命令:grep
- VS 2012 如何发布 ASP.NET 网站到本地IIS
- LiveVideoStack主编观察04 /
- JavaScript内置对象Date----格式化时间
- 修改 decimal 默认值为0.00 sql_书写高性能SQL语句技巧,网友都说好
- python——re模块
- 新车「智能化+安全」进入纵深区,艾拉比OTA成高频词
- 秒杀项目(1)项目环境搭建
- 牛客赛47 DongDong认亲戚(并查集+map)
- 小林和腾讯不得不说的故事(完整篇)
- Ubuntu18.04 域名解析失败
- html5 css图片倒影,CSS3----图片倒影效果
- 我读《写给大家看的设计书》
- Cesium for Unreal 数据加载 场景漫游 粒子效果 视频监控 VR预览
- OpenLayers3 地图图层(Layers) 详解
- 计算机中选中多个文件的快捷键,电脑操作过程中同时选定多个文件的方法
- linux 终端命令行的快捷键列表
- c语言 指针 越界,关于指针错误使用带来的问题――数组越界