Kotlin 1.4.30-RC 密封接口来啦!

密封类是 Kotlin 的老成员了,现在也可以有密封接口了。

Java 的密封接口

我们先来看看 Java 的密封接口是怎么回事吧:

1
2
3
4
5
6
7
sealed interface PlayerState permits Idle, Playing, Error { }

final class Idle implements PlayerState { }

final class Playing implements PlayerState { }

final class Error implements PlayerState { }

功能上,与 Kotlin 的密封类类似,都是限制子类个数的,所以这一点儿不应当有什么理解上的困难。

语法上,Java 秉持着它一贯的“啰嗦”的特点,在密封接口定义时,还要明确写出 permits,告诉大家这个接口只能够被以下几个类实现。你会不会感觉很奇怪,看一下后面这几行不就知道了,为什么还有加一个 permits?因为我们编写 Java 代码的时候,通常一个类就是一个文件,因此 Java 的密封接口不会去限制只能在文件内部定义实现类(就像 Kotlin 那样),因此 permits 是必须的。

我们还注意到,PlayerState 的子类前面都加了个 final 关键字,意思就是不能被继承。这一点与 Kotlin 的密封类语法类似,Kotlin 当中类型默认就是 final 的,大家可能都没有注意过这个限制。

说到这里,如果大家想要体验 Java 的密封接口的特性,需要给编译器添加 --enable-preview 参数,具体在 Gradle 当中可参考以下配置:

1
2
3
compileJava {
it.options.compilerArgs.add('--enable-preview')
}

如果使用 Kotlin 与 Java 15 互调用,在 Kotlin 1.4.30-RC 版本当中需要添加下面的参数:

1
2
3
4
5
6
compileKotlin {
kotlinOptions {
languageVersion = "1.5" // Kotlin 1.5 experimental
freeCompilerArgs += "-Xjvm-enable-preview" // for java preview
}
}

密封类型子类的子类

那么灵魂拷问来了,不加 final 行不行?

三选一,

第一种:sealed,就是你自己也称为密封类,这样子类还是受限制的

第二种: non-sealed,就是明确告诉大家,你不是密封类,而且不是 final,这意味着 Playing 这个类型是可以被其他类型继承的。

啊??那这样子类不就不受限制了吗?

对呀,子类是不受限制了,但直接子类的个数还是有限的。也就是说密封类实际上限制的是直接子类的个数,这一点之前我们很少提到。

第三种,final,这就比较好理解了,直接把子类的路堵死完事儿。

这么看来,Java 除了支持密封接口以外,也是直接密封类的,而且还能允许密封接口或者密封类的 non-sealed 子类有其他子类,看上去是不是比 Kotlin 高级?

非也非也!

Kotlin 的密封类的子类,也可以有子类的!列位请看:

1
2
3
4
5
6
7
8
9
10
class Song
class Options

sealed class PlayerState {
class Error(val t: Throwable): PlayerState()
object Idle: PlayerState()

open class Playing(val song: Song): PlayerState()
class PlayingWithOptions(song: Song, val options: Options): Playing(song)
}

Playing 居然可以有个子类,叫做 PlayingWithOptions!这样搞,是不是密封类的特性就被破坏了呀?

当然不是,密封类的子类除了 Error、Idle 以外,仍然只有一种可能,那就是 Playing。这很好理解,对吧。

Kotlin 的密封接口

好了,接下来我们终于要抬出 1.4.30-RC 当中新增的 Kotlin 的密封接口了,前面的 PlayerState 里面什么都没有,显然我们把它定义成接口更好:

1
2
3
4
5
6
sealed interface PlayerState {
class Error(val t: Throwable): PlayerState
object Idle: PlayerState
open class Playing(val song: Song): PlayerState
class PlayingWithOptions(song: Song, val options: Options): Playing(song)
}

为了配合密封接口的新特性,IDE 在创建 Kotlin 类型的时候也多了个选择:

而且你会神奇的发现,内联类跟密封接口可以一起使用了:

1
2
3
4
5
6
sealed interface PlayerState {
// 注意这里!
inline class Error(val t: Throwable): PlayerState

...
}

我们在上一篇文章里面刚刚说到这事儿,虽然可以这么写,这样做意义并不大。因为密封类的子类在使用的过程中总是会声明成父类,这个过程总是会出现装箱:

1
2
3
val playerState: PlayerState = Idle
...
playerState = Error(...) // 装箱

所以,我们几乎可以认为,内联类在密封类当中使用基本上都是错误的用法。

稍微提一句,官方在 KT-42434 Release inline classes as Stable, secure Valhalla compatibility 当中明确了 inline class 将在 1.4.30 进入 Beta 阶段,在 1.5.0 进入稳定状态;不仅如此,为了配合 Valhalla 的 Value Type 特性,后续内联类计划被改名叫做 value class,这当然都是后面的事儿了,我们后面有机会再聊。


C 语言是所有程序员应当认真掌握的基础语言,不管你是 Java 还是 Python 开发者,欢迎大家关注我的新课 《C 语言系统精讲》:

扫描二维码或者点击链接《C 语言系统精讲》即可进入课程


Kotlin 协程对大多数初学者来讲都是一个噩梦,即便是有经验的开发者,对于协程的理解也仍然是懵懵懂懂。如果大家有同样的问题,不妨阅读一下我的新书《深入理解 Kotlin 协程》,彻底搞懂 Kotlin 协程最难的知识点:

扫描二维码或者点击链接《深入理解 Kotlin 协程》购买本书


如果大家想要快速上手 Kotlin 或者想要全面深入地学习 Kotlin 的相关知识,可以关注我基于 Kotlin 1.3.50 全新制作的入门课程:

扫描二维码或者点击链接《Kotlin 入门到精通》即可进入课程


Android 工程师也可以关注下《破解Android高级面试》,这门课涉及内容均非浅尝辄止,除知识点讲解外更注重培养高级工程师意识:

扫描二维码或者点击链接《破解Android高级面试》即可进入课程