Kotlin 特色 sealed 关键字

2024-05-31 14:04
文章标签 特色 关键字 kotlin sealed

本文主要是介绍Kotlin 特色 sealed 关键字,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

sealed 意为密封的,可修饰类 class 和接口 interface,用来表示受限的继承结构。

Sealed Class

介绍

sealed class,密封类,密封类是一种特殊的抽象类,用于限制可以继承它的子类。

密封类具备最重要的一个特点:

  • 其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与之不同的 module 中,且需要保证 package 一致。

这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的。事实上在早期版本中,只允许在 sealed class 内部或定义的同文件内扩展子类,这些限制在 Kotlin 1.5 中被逐步放开。

sealed class 还具有如下特点或限制:

  1. sealed class 是抽象类,可以拥有抽象方法,无法直接实例化。
  2. sealed class 的构造函数只能拥有两种可见性:默认情况下是 protected,还可以指定成 private,但不允许指定成 public。
  3. sealed class 子类可扩展局部以及匿名类以外的任意类型子类,包括普通 class、data classobject、sealed class 等,子类信息在编译期可知。
  4. sealed class 的实例,可配合 when 表达式进行判断,当所有类型覆盖后可以省略 else 分支。
  5. 当 sealed class 子类没有指定构造方法或定义任意属性的时候,建议定义成单例 object,因为即便实例化成多个实例,互相之间没有状态的区别。在 Kotlin 1.9 版本新增 data object 用于取代 object 的用法,编译器会提示“‘sealed’ sub-object can be converted to ‘data object’ ”,toString() 不会打印 HashCode 等无用信息让输出更有意义。

使用

可以在 sealed class 内部定义子类:

sealed class Language {//定义在内部data object English : Language()data class French(val str: String) : Language()class German(str: String) : Language()
}

还可以在外部定义子类:

sealed class Language {//定义在内部data object English : Language()data class French(val str: String) : Language()class German(str: String) : Language()
}//定义在同一文件中
data object Chinese : Language()
data class Japanese(val str: String) : Language()

此外还可以定义在同包名不同一文件下

//定义在同包名不同一文件下
data class Korean(val str: String) : Language()

对于不同类型的扩展子类,when 表达式的判断亦不同:

  • 判断 sealed class 内部子类类型自然需要指定父类前缀
  • 判断 sealed class 外部子类类型自然无需指定前缀
  • object class 的话可以直接进行实例判断,也可以用 is 关键字判断类型匹配
  • 普通 class 类型的话则必须使用 is 关键字判断类型匹配
    fun demo(language: Language) {when (language) {Language.English -> {}is Language.French -> {}is Language.German -> {}Chinese -> {}is Japanese -> {}is Korean -> {}}}

我们知道Kotlin代码最终会编译成Java字节码的,让我们来看一下,上述的Kotlin代码反编译之后是怎么样的:

public abstract class Language {public /* synthetic */ Language(DefaultConstructorMarker defaultConstructorMarker) {this();}private Language() {}// subclass:data objectpublic static final class English extends Language {public static final English INSTANCE = new English();public boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof English)) {return false;}English english = (English) obj;return true;}public int hashCode() {return -765073291;}public String toString() {return "English";}private English() {super(null);}}// subclass:data classpublic static final class French extends Language {private final String str;public static /* synthetic */ French copy$default(French french, String str2, int i, Object obj) {if ((i & 1) != 0) {str2 = french.str;}return french.copy(str2);}public final String component1() {return this.str;}public final French copy(String str2) {Intrinsics.checkNotNullParameter(str2, "str");return new French(str2);}public boolean equals(Object obj) {if (this == obj) {return true;}return (obj instanceof French) && Intrinsics.areEqual(this.str, ((French) obj).str);}public int hashCode() {return this.str.hashCode();}public String toString() {return "French(str=" + this.str + ')';}public French(String str2) {super(null);Intrinsics.checkNotNullParameter(str2, "str");this.str = str2;}public final String getStr() {return this.str;}}// subclass:classpublic static final class German extends Language {public German(String str) {super(null);Intrinsics.checkNotNullParameter(str, "str");}}
}

可以看到 sealed class 本身被编译为 abstract class,其内部子类按类型有所不同:

  • data object 类型在 class 内部集成了静态的 INSTANCE 实例,同时还有equalstoString 以及 hashCode 函数。
  • data class 类型在 class 内部集成了属性的 getequalscopytoString 以及 hashCode 函数。
  • class 类型仍是普通的 class。

而外部子类和同包名不同一文件下的子类则自然是定义在 Language 抽象类外部,内容也是跟上面一样的。

Sealed Interface

sealed interface,密封接口,和 sealed class 有几乎一样的特点。此外,还有额外的优势,可以帮助密封类、枚举类等类实现多继承和扩展性,比如搭配枚举,以处理更复杂的分类逻辑。

举例:Flappy Bird 游戏的过程中会产生很多 Action 来触发数据的计算以推动 UI 刷新以及游戏的进程,Action 可以用 enum class 来管理。

其中有些 Action 是关联的,有些则没有关联、不是同一层级。但是 enum class 默认扩展自 Enum 类,无法再嵌套 enum。

这将导致层级混乱、阅读性不佳,甚至有的时候功能相近的时候还得特意取个不同的名称。

 enum class Action {Tick,// GameActionStart, Exit, Restart,// BirdActionUp, Down, HitGround, HitPipe, CrossedPipe,// PipeActionMove, Reset,// RoadAction// 防止和 Pipe 的 Action 重名导致编译出错,// 将功能差不多的 Road 移动和重置 Action 定义加上了前缀RoadMove, RoadReset}fun dispatch(action: Action) {when (action) {Action.Tick -> {}Action.Start -> {}Action.Exit -> {}Action.Restart -> {}Action.Up -> {}Action.Down -> {}Action.HitGround -> {}Action.HitPipe -> {}Action.CrossedPipe -> {}Action.Move -> {}Action.Reset -> {}Action.RoadMove -> {}Action.RoadReset -> {}}}

借助 sealed interface 我们可以给抽出 interface,并将 enum 进行层级拆分。更加清晰、亦不用担心重名。

 sealed interface Actionenum class GameAction : Action {Start, Exit, Restart}enum class BirdAction : Action {Up, Down, HitGround, HitPipe, CrossedPipe}enum class PipeAction : Action {Move, Reset}enum class RoadAction : Action {Move, Reset}object Tick: Action

使用的时候就可以对抽成的 Action 进行嵌套判断:

 fun dispatch(action: Action) {when (action) {Tick -> {}is GameAction -> {when (action) {GameAction.Start -> {}GameAction.Exit -> {}GameAction.Restart -> {}}}is BirdAction -> {when (action) {BirdAction.Up -> {}BirdAction.Down -> {}else -> {}}}is PipeAction -> {when (action) {PipeAction.Move -> {}PipeAction.Reset -> {}}}is RoadAction -> {when (action) {RoadAction.Move -> {}RoadAction.Reset -> {}}}}}

sealed 与 enum 的区别

  • enum:enum 只是一个值(常量),每个 enum 常量只能以单例的形式存在。
  • sealed:sealed可以是一个值(定义成 data object 不携带数据),还可以是一个有状态的值(定义成 data class 携带数据)。sealed class 子类可以拥有多个实例,不受限制,每个均可以拥有自己的状态。
  • enum class 不能扩展自 sealed class 以及其他任何 Class,但可以实现 sealed interface,正如上面 Action 的举例。

这篇关于Kotlin 特色 sealed 关键字的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1018135

相关文章

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

c# checked和unchecked关键字的使用

《c#checked和unchecked关键字的使用》C#中的checked关键字用于启用整数运算的溢出检查,可以捕获并抛出System.OverflowException异常,而unchecked... 目录在 C# 中,checked 关键字用于启用整数运算的溢出检查。默认情况下,C# 的整数运算不会自

Oracle Start With关键字

Oracle Start With关键字 前言 旨在记录一些Oracle使用中遇到的各种各样的问题. 同时希望能帮到和我遇到同样问题的人. Start With (树查询) 问题描述: 在数据库中, 有一种比较常见得 设计模式, 层级结构 设计模式, 具体到 Oracle table中, 字段特点如下: ID, DSC, PID; 三个字段, 分别表示 当前标识的 ID(主键), DSC 当

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

JavaScript 根据关键字匹配数组项

要在JavaScript数组中根据关键字匹配项,可以使用filter方法结合一个测试函数。以下是一个示例代码,定义了一个函数findByKeyword,该函数接受一个数组和一个关键字,然后返回一个新数组,其中包含与关键字匹配的所有项。 function findByKeyword(array, keyword) {return array.filter(item => {// 假设要匹配的是对象

MySQL 的关键字

MySQL 中的关键字是数据库中具有特殊含义的保留字,它们用于定义数据库结构、操作数据库数据和控制数据库行为。关键字在 MySQL 查询中扮演着至关重要的角色,因为它们是 SQL 语句的核心组成部分。 1. 数据定义语言 (DDL) 关键字 数据定义语言 (DDL) 关键字用于定义、修改和删除数据库结构,如数据库、表和索引等。这些关键字通常用于创建、删除表结构以及修改表的列等操作。 1.1

C++中的mutable关键字详解

目录 1.概述 2.使用场景 3.示例 4.mutable修饰Lambda表达式 5.注意事项 1.概述         在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。         我们知道,被const关键字修饰的函数的一个重要作用就是为了能够保护类中的成员变量。即:该函数可以

[Python]生成器和yield关键字

生成器和yield关键字 1.生成器介绍: 概述: ​ 它指的是 generator, 类似于以前学过的: 列表推导式, 集合推导式, 字典推导式… 作用: ​ 降低资源消耗, 快速(批量)生成数据. 实现方式: ​ 1.推导式写法. my_generator = (i for i in range(5)) ​ 2.yield写法. def get_generator():for i

Kotlin高阶函数与Lambda表达式及内联函数的介绍

目录 1、高阶函数1.1、什么是高阶函数?1.1.1、不带返回值的高阶函数1.1.2、带参数且带返回值的高阶函数1.1.3、与一般的函数进行比较 1.2、如何使用?1.3、高阶函数有什么作用? 2、Lambda表达式2.1、什么是Lambda表达式?2.1.1、无参数的写法2.1.2、有参数的写法2.1.3、有参数且有返回值的写法 2.2、如何使用?2.3、Lambda表达式有什么作用? 3

java基础总结11-面向对象7(super关键字)

在JAVA类中使用super来引用父类的成分,用this来引用当前对象,如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。怎么去引用里面的父类对象呢?使用super来引用,this指的是当前对象的引用,super是当前对象里面的父对象的引用。 1 super关键字测试 package cn.galc.test;/*** 父类* @autho