Kotlin 中使用交叉类型与联合类型
之后文章会全部转到飞书文档发布,此网页可能存在更新不及时的问题,飞书文档首页 zsqw123 Homepage
Kotlin 中使用交叉类型与联合类型
强类型界著名的 Typescript 语言就支持相当丰富的类型操作符,如联合(Union)与交叉(Intersection),但 JVM 是没有的,所以 kotlin 也没做,但在编译器阶段是有做支持的,即编译器事实上处理了这种情况,但没有暴露给语言使用者。
交叉类型(Intersection Type)
比如在下面的例子中,kotlin 会将类型推断为 Comparable<*> & Serializable
,这就是一种交叉类型。
|
|
但这个例子对我们没什么大用,毕竟又不是得到了 Int & String
类型,得到的仅仅是他们的几个公共父类型的交叉,这对我们意义就变得小很多了。
但很显然本文一定会提出解决方案,不然写这篇文章就没什么意思了。
事情的转机出现在 kotlin 支持 context receiver 这一特性,通过它我们能让 this 隐式具有多个父类,这不就行交叉类型吗?于是就有了下面的扩展函数:
|
|
常规的 Kotlin with 函数仅支持传入单个参数,不过在我们对其进行了一些魔改之后,其可以支持传入多个参数,并且 this 的类型也可以认为变成了 V1 & V2 & V3
这样的类型,因为我们通过 context
标记了 block 的 this 同时具备多层的隐式上下文,那么在使用处你就可以这么用:
|
|
这样就能够直接通过 this 访问到 Foo、Bar、Baz、Qux 内部的成员 foo、bar、baz 和 qux,即我们得到了这四种类型的交叉。
不过如果你真的尝试了上面的代码,那么编译器一定会给你报错:
也有相关的 Youtrack:https://youtrack.jetbrains.com/issue/KT-54233 ,其原因是因为 Kotlin 目前还没有一种方式能确保 V1 和 V2 之间不存在继承关系,因为一旦他们存在继承关系的话,这里使用时就不知道应当使用哪一个上下文参数的方法了,编译器会很疑惑:究竟该使用 v1 还是 v2 的方法作为 this 呢?
不过依照目前编译器的实现(Kotlin 2.0)来说,是看顺序的,v1 会优先于 v2 被使用,在这里我会继续等待官方更合理的解法,不过对于我们要实现交叉类型这一点来说,我们可以简单一点,用一行代码就可以绕过这个错误:
|
|
具体的代码可以移步 https://github.com/zsqw123/kt-little/blob/master/src/main/java/com/zsu/multipleWith.kt
不过,需要注意的是这种类型并不能被传播到 with 外部,交叉类型只能在 lambda 内部操作
此外,对于接口类型,泛型的 where 也可以实现类似的效果,这里不做展开。
联合类型(Union Type)
在 Kotlin 中使用联合类型需要付出抽象的成本,即使用 sealed class:
|
|
此时 Union 的类型即为 Foo | Bar
,通过 sealed class 能够保证子类型只能为固定的几个类型。但 union type 的缺陷是其只能使用当前模块内定义的类,如果需要将其他模块定义的类(如第三方库或 stdlib 里面的类)就需要进行包装:
|
|
这也就是上文所提到的付出“抽象的成本”,对于外部的类都必须做一层包装来达到伪 union,并且 Kotlin 也没有类似 Rust 的 Deref 自动解引用,调用时需要再调用一下其内部包装的属性,确实不优雅,但目前也只能这样去做。
|
|
此外,如果用了 value class,部分代码在部分场景下会得到一定的性能提升,能够减少一部分的抽象开销。
其它
此外,Kotlin 2.0 之后也有类似的打算来推进不同分类的 union type,详见:Kotlin 2.0 更新速览与 2.1、2.2+ 展望,但目前还是未实现的特性。
- 飞书文档原文:Kotlin 中使用交叉类型与联合类型
- 个人主页:Base / Homepage / Main