![](https://img.haomeiwen.com/i1479838/64ecbe49d94c86d7.png)
这是 Sealed Classes(密封类)系列的第三篇文章,之前的文章从原理、优化、实战以不同的角度分别介绍了 Sealed Classes 的强大。
在 Kotlin Sealed 是什么?为什么 Google 都在用 文章中主要包含以下内容:
- Sealed Classes 原理分析?
- 枚举、抽象类、Sealed Classes 分别有那些优缺点?
- 分别在什么情况下使用枚举和 Sealed Classes?
- 为什么 Sealed Classes 用于表示受限制的类层次结构?
- 在项目中如何使用 Sealed Classes?
- 禁止在 Sealed Classes 所定义的文件外使用, Kotlin 是如何做到的呢?
- ......
在 Kotlin 中的密封类 优于 带标签的类 文章中主要包含以下内容:
- 什么是 Tagged Classes(标记类)?
- Tagged Classes 的优缺点,以及在项目中所带来的影响?
- 如何使用 Sealed Classes 优化现有的代码,可以带来那些收益?
而这篇文章主要来介绍 Sealed Classes 在 Kotlin 1.5.0 上带来的新特性及原理分析,在开始分析之前,先来简单的回顾一下之前文章的内容。
Sealed Classes
什么是 Sealed Classes?
Sealed Classes 用于表示受限制的类层次结构,其实这句话可以拆成两句话来理解。
- Sealed Classes 用于表示层级关系: 子类可以是任意的类, 数据类、Kotlin 对象、普通的类,甚至也可以是另一个 Sealed
- Sealed Classes 受限制: 必须在同一文件中,或者在 Sealed Classes 内部中使用,在 Kotlin 1.1 之前,规则更加严格,子类只能在 Sealed Classes 内部中使用
Sealed Classes 的优点:
- Sealed Classes 使类之间的职责分明,提高代码的可读性
- 扩展性强,可以在不修改原有的代码结构的基础上添加新的参数或者子类
- 每个类中不包含无关的字段,在一定程度上减少对象所占用的内存
- Sealed Classes 结合 when 表达式一起使用会更加的方便,when 语句下的所有分支可以通过快捷键
Mac/Win/Linux:Alt + Enter
自动生成,效果如下所示:
![](https://img.haomeiwen.com/i1479838/1e1b67e14cab705f.gif)
这里只是简单的回顾了一下之前的内容,接下来我们一起来看看,在新版本 Kotlin 1.5.0 中 Sealed Classes 给我们带来了那些优化。
Sealed Classes 进化了
在 2021 年 5 月份,Kotlin 官方发布了 1.5.0,在这个版本中带来几个非常有用的特性,而在这篇文章我们主要介绍 Sealed Classes,更多信息请查看 What's new in Kotlin 1.5.0。
在 Kotlin 1.0 时,子类只能在 Sealed Classes 内部中使用,因为 Sealed class 会被编译成 abstract class,并且默认的构造方法被私有化了,所以子类必须嵌套在 Sealed Classes 类中。
在 Kotlin 1.1 时,允许顶级的 Sealed Classes 和它顶级子类在同一个文件中,因为编译器会自动生成了一个 公有 的构造方法,在子类的构造方法中调用了父类 公有 的构造方法,而这些都是 Kotlin 编译器帮我们做的。
在 Kotlin 1.5.0 中,放宽了对 Sealed Classes 限制,只需要保证 Sealed Classes 和它的子类,在同一个包名和 module 下面即可,这些都是 Kotlin 编译器帮我们做的。
接下里我们一起来分析一下为什么 Kotlin 需要升级 Sealed Classes?在之前的文章 Kotlin 中的密封类 优于 带标签的类 分析了 Sealed Classes 有很多优点,但是它也有很多不足之处,这也是为什么需要升级 Sealed Classes 的原因:
- Sealed Classes 的子类,被限制在单个父类中
- 把所有的代码放在一个文件中,会造成文件臃肿,可读性下降,如下所示
sealed class Color {
class Red : Color()
class Blue : Color()
// ...... 更多的颜色
}
sealed class Figure {
abstract fun draw()
}
class Rectangle(val color: Color) : Figure() {
override fun draw() {
}
}
class Round(val color: Color) : Figure() {
override fun draw() {
}
}
class Triangle(val color: Color) : Figure() {
override fun draw() {
}
}
...... // 随着需求的增加,文件会越发庞大
// 通过快捷键 Mac/Win/Linux:Alt + Enter 自动生成
fun drawFigure(figure: Figure) {
when (figure) {
is Rectangle -> TODO()
is Round -> TODO()
is Triangle -> when (figure.color) {
is Color.Blue -> TODO()
is Color.Red -> TODO()
}
}
}
在 Kotlin 1.1 中被限制在一个文件中,随着需求的增加,文件只会越发的庞大,现在放宽了限制之后,我们可以将子类拆分成单独的文件,可以进一步的提高代码的可读性。
- 只允许顶级的 Sealed Classes 和它顶级的子类在同一个文件中,对于非顶级的 Sealed Classes,所有子类都应该在其内部声明,以下代码编译会失败
![](https://img.haomeiwen.com/i1479838/b309075001c816f3.png)
正如你所见 Sealed Classes 还是不够灵活,非顶级子类 Yellow 定在 Color 的外面,编译就会出错,而在 Koltin 1.5.0 放宽了限制之后,以上代码可以正常编译通过,不仅提高代码的可读性,而且灵活性也提高了。
除此之外还允许在 Sealed Classes 中声明受保护的构造函数。在 1.5.0 之前所有 Sealed Classes 的默认构造函数都是 private,但是在 1.5.0 之后默认构造函数都是 protected。
sealed class Figure {
constructor() // 默认为 protected
private constructor(area: Double) : this()
}
但是不允许声明为 public,因为编译器会自动生成,如果声明为 public 编译就会出错:
![](https://img.haomeiwen.com/i1479838/a7ab289d39f985e1.png)
一起来看看声明为 protected 的构造函数,反编译后的 Java 代码都做了什么。Tools → Kotlin → Show Kotlin Bytecode
。
public abstract class Figure {
private Figure() {
}
private Figure(double area) {
this();
}
// $FF: synthetic method
public Figure(DefaultConstructorMarker $constructor_marker) {
this();
}
// $FF: synthetic method
public Figure(double area, DefaultConstructorMarker $constructor_marker) {
this(area);
}
}
public final class Rectangle extends Figure {
public Rectangle() {
super(1.0D, (DefaultConstructorMarker)null);
}
}
正如你所见,生成的构造方法还是私有的,只不过编译器会自动生成 公有 的构造方法,在子类的构造方法中调用了父类 公有 的构造方法。除此变化之外,另外还有一个重要的特性,增加了密封接口(Sealed Interface)。
注意:
如果你已经升级到 Kotlin 1.5.0,编译器还提示出错,请将下面的代码添加在 build.gradle 中。
compileKotlin {
kotlinOptions {
languageVersion = "1.5"
}
}
Sealed Interface
Sealed Interface 和 Sealed Classes 一样都是用于表示受限制的类层次结构,所以它也拥有 Sealed Classes 所有的优点,Sealed Classes 拥有的特性 Sealed Interface 也都拥有。
但是不同之处在于 Sealed Classes 被限制在单个父类中,而 Sealed Interface 支持更灵活的受限制类层次结构,因为子类可以实现多个 Sealed Interface,如下所示。
// IColor.kt
sealed interface IColor
class Red : IColor
class Blue : IColor
// IArea.kt
sealed interface IArea {
fun area(): Double
}
// IFigure.kt
sealed interface IFigure
class Round() : IFigure, IColor
class Rectangle(val length: Double, val width: Double) : IFigure, IArea, IColor {
override fun area(): Double = length * width
}
Sealed Interface 允许子类有多个实现,自由度更高,使得代码更加的灵活,但是它和 Sealed Classes 一样,被限制在了同一个包名和 module 下面,如果违反这个限制编译就会出错。
![](https://img.haomeiwen.com/i1479838/afb76e420ec7648b.png)
关于 Sealed Interface 相关的内容,就先介绍到这里,这篇文章主要分析了 Sealed Classes 以及 Sealed Interface 优缺点,在 Kotlin 1.5.0 中还增加了其他的特性,将会在后续的文章中介绍。
全文到这里就结束了,如果有帮助 点个赞 就是对我最大的鼓励
代码不止,文章不停
最后推荐我一直在更新维护的项目和网站:
-
计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:AndroidX-Jetpack-Practice
-
LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
![](https://img.haomeiwen.com/i1479838/421ddf9c7ec7b7d6.png)
-
剑指 offer 及国内外大厂面试题解:在线阅读
-
LeetCode 系列题解:在线阅读
-
最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看 Android10-Source-Analysis
-
整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看 Technical-Article-Translation
-
「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看 为互联网人而设计导航网站
网友评论