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相关推荐

  1. Kotlin contract 用法及原理

    什么是 contract contract(契约)是一种 Kotlin 面向编译器约定的一种规则,它帮助编译器更加智能地识别某些需要特定的代码条件,为代码创建更加友好的上下文关联环境. Kotlin ...

  2. Kotlin Contract(契约)

    虽然kotlin有智能推断功能,但是有时候还是有些代码没有办法进行推断,因此,诞生了Contract(契约)功能,不过目前该功能还是实验功能(ExperimentalContracts),其使用可以参 ...

  3. 探索 Kotlin 协程原理

    接下来跟大家分享一下我在了解 Kotlin 协程实现的过程中理解的一些概念,如果你发现哪些地方我说错了的话,欢迎提出你的理解. 1. Kotlin 协程原理概述 Kotlin 协程的大致的执行流程如上 ...

  4. java面试题_阿里大厂流出的数百道 Java 经典面试题

    BAT 常问的 Java基础39道常见面试题 1.八种基本数据类型的大小,以及他们的封装类 2.引用数据类型 3.Switch能否用string做参数 4.equals与==的区别 5.自动装箱,常量 ...

  5. Kotlin 标准库随处可见的 contract 到底是什么?

    Kotlin 的标准库提供了不少方便的实用工具函数,比如 with, let, apply 之流,这些工具函数有一个共同特征:都调用了 contract() 函数. @kotlin.internal. ...

  6. Kotlin let、with、run、apply、also函数的使用

    let,with,run,apply,also 是内联扩展函数 下面是自己使用的心的如果有错的地方希望给予指正谢谢 这几个主要用来简化操作,使得代码可读性提高 ,下面列举项目中使用效果 1 let 先 ...

  7. 【Kotlin】apply 内联扩展函数 ( apply 函数原型 | apply 函数示例 | Kotlin 调用 Java API )

    文章目录 I . 内联扩展函数 apply II . Kotlin 调用 Java API III . apply 内联扩展函数示例 ( 调用 Java API 处理图像 ) I . 内联扩展函数 a ...

  8. [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)

    简述: 不知道是否有小伙伴还记得我们之前的Effective Kotlin翻译系列,之前一直忙于赶时髦研究Kotlin 1.3中的新特性.把此系列耽搁了,赶完时髦了还是得踏实探究本质和基础,从今天开始 ...

  9. Kotlin极简教程:第4章 基本数据类型与类型系统

    原文链接:https://github.com/EasyKotlin 到目前为止,我们已经了解了Kotlin的基本符号以及基础语法.我们可以看出,使用Kotlin写的代码更简洁.可读性更好.更富有生产 ...

最新文章

  1. [更新问题]无法在安装新的版本前,为“./boot/vmlinuz-2.6.24-19-generic”做一个符号链接备份...
  2. [Contest20171109]函数(lipshitz)
  3. linux命令:grep
  4. VS 2012 如何发布 ASP.NET 网站到本地IIS
  5. LiveVideoStack主编观察04 /
  6. JavaScript内置对象Date----格式化时间
  7. 修改 decimal 默认值为0.00 sql_书写高性能SQL语句技巧,网友都说好
  8. python——re模块
  9. 新车「智能化+安全」进入纵深区,艾拉比OTA成高频词
  10. 秒杀项目(1)项目环境搭建
  11. 牛客赛47 DongDong认亲戚(并查集+map)
  12. 小林和腾讯不得不说的故事(完整篇)
  13. Ubuntu18.04 域名解析失败
  14. html5 css图片倒影,CSS3----图片倒影效果
  15. 我读《写给大家看的设计书》
  16. Cesium for Unreal 数据加载 场景漫游 粒子效果 视频监控 VR预览
  17. OpenLayers3 地图图层(Layers) 详解
  18. 计算机中选中多个文件的快捷键,电脑操作过程中同时选定多个文件的方法
  19. linux 终端命令行的快捷键列表
  20. c语言 指针 越界,关于指针错误使用带来的问题――数组越界

热门文章

  1. 做库存功能业务场景详解,S2B2B系统助力建筑建材企业精准掌握库存动态
  2. 小程序左上角返回按钮自定义跳转
  3. eclipse xml editor从视图编辑改为源代码编辑
  4. 朋友SanJone的时间线
  5. 从数字货币估值学习区块链投资(行业观察)
  6. 32.将字符串转换为数字
  7. DataGrid用法集锦
  8. 使用python3爬取豆瓣电影top250
  9. 【226期】面试问我,HashMap 的默认初始容量是多少,我该怎么说?
  10. 自学 Python 3 最好的 入门 书籍 推荐(附 免费 在线阅读 下载链接)