# Kotlin 中使用交叉类型与联合类型


> 之后文章会全部转到飞书文档发布，此网页可能存在更新不及时的问题，飞书文档首页 [zsqw123 Homepage](https://eqyrx3fg3l.feishu.cn/docx/TkWidN8RtoLK4ix1NRRcWpdmnQf)

# Kotlin 中使用交叉类型与联合类型

强类型界著名的 Typescript 语言就支持相当丰富的类型操作符，如联合（Union）与交叉（Intersection），但 JVM 是没有的，所以 kotlin 也没做，但在编译器阶段是有做支持的，即编译器事实上处理了这种情况，但没有暴露给语言使用者。

## 交叉类型（Intersection Type）

比如在下面的例子中，kotlin 会将类型推断为 `Comparable<*> & Serializable`，这就是一种交叉类型。

```kotlin
val a = if (condition) 1 else ""
```

但这个例子对我们没什么大用，毕竟又不是得到了 `Int & String` 类型，得到的仅仅是他们的几个公共父类型的交叉，这对我们意义就变得小很多了。

但很显然本文一定会提出解决方案，不然写这篇文章就没什么意思了。

事情的转机出现在 kotlin 支持 [context receiver](https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md) 这一特性，通过它我们能让 this 隐式具有多个父类，这不就行交叉类型吗？于是就有了下面的扩展函数：

```kotlin
inline fun <V1, V2, R> with(v1: V1, v2: V2, block: context(V1, V2) () -> R) = block(v1, v2)

inline fun <V1, V2, V3, R> with(v1: V1, v2: V2, v3: V3, block: context(V1, V2, V3) () -> R) = block(v1, v2, v3)

inline fun <V1, V2, V3, V4, R> with(v1: V1, v2: V2, v3: V3, v4: V4, block: context(V1, V2, V3, V4) () -> R) = block(v1, v2, v3, v4)
```

常规的 Kotlin with 函数仅支持传入单个参数，不过在我们对其进行了一些魔改之后，其可以支持传入多个参数，并且 this 的类型也可以认为变成了 `V1 & V2 & V3` 这样的类型，因为我们通过 `context` 标记了 block 的 this 同时具备多层的隐式上下文，那么在使用处你就可以这么用：

```kotlin
class Foo(val foo: Int = 1)
class Bar(val bar: Int = 2)
class Baz(val baz: Int = 3)
class Qux(val qux: Int = 4)

fun main() {
    with(Foo(), Bar(), Baz(), Qux()) {
        println("$foo $bar $baz $qux")
    }
}
```

这样就能够直接通过 this 访问到 Foo、Bar、Baz、Qux 内部的成员 foo、bar、baz 和 qux，即我们得到了这四种类型的交叉。

不过如果你真的尝试了上面的代码，那么编译器一定会给你报错：

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1b63905df484a5ab02974d1e4464bc5~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1416&h=152&s=32770&e=png&b=2b2f37)

也有相关的 Youtrack：https://youtrack.jetbrains.com/issue/KT-54233 ，其原因是因为 Kotlin 目前还没有一种方式能确保 V1 和 V2 之间不存在继承关系，因为一旦他们存在继承关系的话，这里使用时就不知道应当使用哪一个上下文参数的方法了，编译器会很疑惑：究竟该使用 v1 还是 v2 的方法作为 this 呢？

不过依照目前编译器的实现（Kotlin 2.0）来说，是看顺序的，v1 会优先于 v2 被使用，在这里我会继续等待官方更合理的解法，不过对于我们要实现交叉类型这一点来说，我们可以简单一点，用一行代码就可以绕过这个错误：

```kotlin
@Suppress("SUBTYPING_BETWEEN_CONTEXT_RECEIVERS")
```

具体的代码可以移步 https://github.com/zsqw123/kt-little/blob/master/src/main/java/com/zsu/multipleWith.kt

> 不过，需要注意的是这种类型并不能被传播到 with 外部，交叉类型只能在 lambda 内部操作

* * *

此外，对于接口类型，泛型的 where 也可以实现类似的效果，这里不做展开。


## 联合类型（Union Type）

在 Kotlin 中使用联合类型需要付出抽象的成本，即使用 sealed class：

```kotlin
sealed interface Union

class Foo : Union
class Bar : Union
```

此时 Union 的类型即为 `Foo | Bar`，通过 sealed class 能够保证子类型只能为固定的几个类型。但 union type 的缺陷是其只能使用当前模块内定义的类，如果需要将其他模块定义的类（如第三方库或 stdlib 里面的类）就需要进行包装：

```kotlin
class Foo(content: String) : Union
class Bar(i: Int) : Union
```

这也就是上文所提到的付出“抽象的成本”，对于外部的类都必须做一层包装来达到伪 union，并且 Kotlin 也没有类似 Rust 的 Deref 自动解引用，调用时需要再调用一下其内部包装的属性，确实不优雅，但目前也只能这样去做。

```kotlin
when (union) {
    is Foo -> union.content
    is Bar -> union.i
}
```
此外，如果用了 value class，部分代码在部分场景下会得到一定的性能提升，能够减少一部分的抽象开销。


## 其它

此外，Kotlin 2.0 之后也有类似的打算来推进不同分类的 union type，详见：[Kotlin 2.0 更新速览与 2.1、2.2+ 展望](https://eqyrx3fg3l.feishu.cn/docx/I9BUdZ39zoyYZ5xXJaWcb8Shn4f)，但目前还是未实现的特性。


-   飞书文档原文：[Kotlin 中使用交叉类型与联合类型](https://eqyrx3fg3l.feishu.cn/docx/GuNOdT4VVoywhOxPCF6cOMP0nIe)
-   个人主页：[Base / Homepage / Main](https://eqyrx3fg3l.feishu.cn/docx/TkWidN8RtoLK4ix1NRRcWpdmnQf)


