Kotlin——数据类和密封类

本文介绍了Kotlin中的数据类概念,包括数据类的定义、自动提供的成员函数、拷贝功能及解构声明的使用。此外,还探讨了密封类的概念及其在when表达式中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据类

有些类只是用来持有数据的。在这样的类中,一些标准的函数通常是从数据中推导出来的。在Kotlin中,这些类成为数据类并且以data作为标记:

data class User(val name: String, val age: Int)

编译器自动从主构造器中的属性导入下面这些成员函数:
- equals()/hashCode()
- toString()(形式为User(name=John,age=42))
- componentN()函数对应着声明的属性顺序
- copy()函数

如果上面的函数显式地定义了或者继承自父类,那么将不会自动创建。
为了确保生成的代码的一致性和有意义的行为,数据类必须满足以下需求:
- 主构造器至少有一个参数
- 主构造器的参数必须以valvar标记
- 数据类不可以是abstract、open、sealed或者inner
- (在1.1之前)数据类只能实现接口

从1.1开始,数据类可以继承自其它类。
在JVM上,如果生成的类需要有一个没有参数的构造器,那么所有属性必须指定默认值。比如:

data class User(val name: String = "", val age: Int = 0)

拷贝

经常有这样一种场景,我们需要从复制某个对象,然后修改某些属性,并且保持原有对象的不变。这就是copy()函数出现的原因。对于上面的User类,它的实现如下:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

使用的话可以如下:

 val jack = user.copy(name = "jack", age = 26)

数据类和解析声明

为数据类生成的 Component 函数 使它们可在解构声明中使用:

val user = User("wangli", 25)
    val (name, age) = user
    println("$name,$age years of age") //输出wangli,25 years of age

标准数据类

标准库提供了PairTriple。在大多数情况下,命名数据类是一个更好的设计选择,因为他们通过为属性提供更有意义的命名来增强可读性。

密封类

密封类用于表示受限制的类层次结构,当一个值只能在一个集合中取值时,而不能取其他值时。在某种意义上,这是枚举类的扩展:枚举类型的值集合
也是受限的,但每个枚举常量只存在一个实例,而密封类
的一个子类可以有可包含状态的多个实例。
为了声明密封类,需要在类名之前使用sealed作为修饰符。一个密封类是可以有子类的,但是所有的子类必须和父类生命在同一个文件中。

sealed class Expr

data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

需要注意的是,密封类的非直接继承的子类可以声明在其他文件中。
密封类的主要好处在于使用when表达式。如果能够
验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。

fun eval(expr: Expr): Double = when (expr) {

    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN

//else被忽略,因为覆盖了所有可能性

}

说明

关于代码请参考我的Github地址

### Kotlin 密封类与枚举类的区别 #### 定义与基本概念 在 Kotlin 中,密封类(Sealed Classes)枚举类(Enum Classes)都是用于表示有限集合的工具。然而,两者的设计目标适用场景存在显著差异。 - **枚举类**是一种特殊的类形式,它允许定义一组固定的常量值。这些常量值是该类的唯一实例[^2]。 - **密封类**则是一个抽象的概念容器,它可以拥有多个具体的子类,但这些子类必须在同一文件中声明。通过这种方式,密封类能够表达更复杂的层次结构并支持多种不同的实现方式[^1]。 --- #### 成员属性与行为能力 - 枚举类中的每一个成员本质上只是一个对象实例,并且无法携带额外的状态或复杂的行为逻辑。如果需要扩展其功能,则可以通过 `object` 的方式为其单独添加方法或者字段[^2]。 - 而对于密封类来说,它的各个具体子类不仅可以继承父类的功能,还可以进一步自定义自己的状态数以及独特的方法实现。这种灵活性使得密封类更适合用来描述那些具有不同表现形态的对象群组[^2]。 ```kotlin // 枚举类示例 enum class Color { RED, GREEN, BLUE; fun description(): String = when (this) { RED -> "Red color" GREEN -> "Green color" BLUE -> "Blue color" } } // 密封类示例 sealed class Result { data class Success(val value: Int) : Result() object Failure : Result() override fun toString(): String = when (this) { is Success -> "Success with $value" is Failure -> "Operation failed" } } ``` --- #### 使用范围与典型应用场景 - 当只需要简单地列举一些选项而不涉及任何动态变化的数据时,应该优先考虑使用枚举类。比如颜色的选择、星期几等等这样的情况非常适合作为枚举项来管理。 - 如果面临的是更加灵活的需求——即不仅限于静态列表而且还需要根据不同条件做出相应调整的话,则应当选用密封类作为解决方案之一[^1]。例如解析网络请求的结果可能成功也可能失败;又或者是游戏开发过程中角色的动作类型既包含了基础动作也涵盖了高级技能释放等多种可能性等情形下都可以借助密封类构建清晰易懂的业务模型[^1]。 --- #### 总结比较表 | 特性/类别 | 枚举类 | 密封类 | |------------------|-------------------------------------|---------------------------------------| | 实例数量 | 只能有一个单一实例 | 子类可有多个独立实例 | | 数据承载能力 | 不具备数据存储 | 支持携带任意数量及种类的数据 | | 方法定义权限 | 所有成员共享同一套公共API | 各个子类均可自由定制专属操作接口 | | 文件位置约束 | 无特别限制 | 必须与其直接派生的所有子类位于同一个源码单元内 | --- ### 示例代码展示 以下是两个实际应用的例子分别展示了如何利用这两种机制解决问题: ```kotlin fun processColor(color: Color): Unit = println(color.description()) val colorsList = listOf(Color.RED, Color.GREEN, Color.BLUE) colorsList.forEach { c -> processColor(c) } ``` 上面这段程序片段演示了怎样基于预设好的几种色彩来进行统一处理流程调用。 相比之下下面这个例子则是关于采用密封类解决更为棘手状况下的策略模式设计思路: ```kotlin fun handleResult(result: Result): String = result.toString() val resultsSequence = sequenceOf(Result.Success(42), Result.Failure) resultsSequence.map(::handleResult).forEach(::println) ``` 在这里我们看到即使面对不确定性的输入也能依靠精心规划过的分类体系得到妥善处置结果反馈给用户端呈现出来。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值