Kotlin 特殊类型篇:密封类比枚举好使在哪?Nothing 到底是个啥?
这一篇聊几个 Kotlin 里比较特殊的类型,它们各有各的用途,理解透了能让代码更优雅。
1. 密封类:类型安全的「枚举升级版」
为什么需要密封类
枚举的局限:每个枚举常量只能有一个实例,无法携带不同数据。
kotlin
// 枚举:所有实例都是单例,无法携带不同数据
enum class ExpressStatus {
TO_BE_SHIPPED,
IN_TRANSIT, // ❌ 没法表示「在哪」
SIGNED // ❌ 没法表示「谁签收」
}
密封类的优势:子类可以是 data class,携带不同数据。
密封类基本用法
kotlin
sealed class ExpressStatus
// 无数据的单例
object ToBeShipped : ExpressStatus()
// 带数据的数据类
data class InTransit(val location: String) : ExpressStatus()
data class Signed(val receiver: String, val time: String) : ExpressStatus()
配合 when 使用:编译器自动检查
这是密封类最大的价值------穷尽性检查:
kotlin
fun showStatus(status: ExpressStatus) {
when (status) {
ToBeShipped -> println("待发货")
is InTransit -> println("运输中:${status.location}")
is Signed -> println("签收人:${status.receiver}")
// 编译器保证覆盖了所有情况,不用写 else
}
}
关键:如果漏掉任何一个子类,编译器直接报错。
实战:网络请求状态(最常用场景)
kotlin
sealed class Result<out T> {
object Loading : Result<Nothing>()
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
// ViewModel 中的用法
sealed class HomeUiState {
object Loading : HomeUiState()
data class Success(val banners: List<Banner>) : HomeUiState()
data class Error(val msg: String) : HomeUiState()
}
2. 枚举类:有限的选项
基本用法
kotlin
enum class Color {
RED, GREEN, BLUE
}
// 带属性和方法
enum class Status(val desc: String) {
SUCCESS("成功"),
FAILURE("失败"),
LOADING("加载中");
fun isSuccess() = this == SUCCESS
}
枚举的实用方法
scss
// 通过名字获取枚举值
val status = Status.valueOf("SUCCESS")
// 遍历所有枚举值
Status.values().forEach { println(it) }
// Kotlin 简化写法
enumValues<Status>().forEach { }
// 获取枚举名称和序号
status.name // "SUCCESS"
status.ordinal // 0
3. Pair 和 Triple:临时数据容器
Pair:两个数据
arduino
// 创建方式
val pair1 = Pair("苹果", 5.5)
val pair2 = "苹果" to 5.5 // 中缀写法,更简洁
// 访问
pair.first // "苹果"
pair.second // 5.5
// 解构
val (name, price) = pair
Triple:三个数据
kotlin
// 创建
val triple = Triple("张三", 25, true)
// 访问
triple.first // "张三"
triple.second // 25
triple.third // true
// 解构
val (name, age, isVip) = triple
使用场景
kotlin
// 函数返回多个值
fun getUserInfo(): Pair<String, Int> {
return "张三" to 25
}
val (name, age) = getUserInfo()
⚠️ 注意:优先用命名数据类
Pair/Triple 属性名 first/second/third 没有业务含义,代码可读性差:
kotlin
// ❌ 不推荐:可读性差
fun login(): Triple<String, Long, Boolean> {
return Triple("token", 1001L, true)
}
val (token, userId, isVip) = login()
// ✅ 推荐:命名数据类
data class LoginResult(
val token: String,
val userId: Long,
val isVip: Boolean
)
fun login(): LoginResult = LoginResult("token", 1001L, true)
val result = login()
什么时候用 Pair/Triple:
- 临时、局部使用,不对外暴露
- 数据简单,没有明确业务绑定
- 快速原型开发
4. Nothing:永远不返回的类型
是什么
Nothing 是 Kotlin 的特殊类型,表示「永远不会有返回值」。
典型场景
kotlin
// 抛异常的函数
fun throwError(): Nothing {
throw IllegalStateException("错误")
}
// TODO 函数的返回类型
val name: String = TODO("稍后实现")
Nothing 的特点
kotlin
// 1. 没有实例
// val nothing = Nothing() ❌ 编译报错
// 2. 是所有类型的子类型
val str: String = TODO() // 可以,因为 Nothing 是 String 的子类型
val int: Int = TODO() // 也可以
5. 内联类:类型别名增强版
kotlin
// 类型别名:编译后是 String
typealias UserId = String
// 内联类:有类型安全,编译后是 String
@JvmInline
value class UserId(val value: String)
内联类的优势:类型安全 + 无性能损耗。
less
@JvmInline
value class UserId(val value: String)
@JvmInline
value class OrderId(val value: String)
fun getUser(id: UserId) { }
fun getOrder(id: OrderId) { }
// 编译器会检查类型
getUser(UserId("123")) // ✅
getUser(OrderId("123")) // ❌ 编译报错,类型不匹配
常见问题
Q:密封类和枚举怎么选?
A:
- 选项有限、固定,且不需要携带不同数据 → 枚举
- 选项有限,但不同选项需要携带不同数据 → 密封类
- 需要穷尽性检查 → 密封类
Q:密封类可以是 data class 吗?
A:可以。密封类的子类可以是 data class,享受数据类的所有特性(equals、hashCode、toString、copy)。
总结
表格
| 类型 | 用途 | 特点 |
|---|---|---|
| 密封类 | 表示有限的、固定的几种类型 | 子类可带数据,配合 when 穷尽检查 |
| 枚举 | 表示有限的、固定的几选项 | 所有实例是单例 |
| Pair/Triple | 临时承载 2-3 个数据 | 属性无业务含义,临时场景用 |
| Nothing | 表示永远不返回 | 抛异常、TODO |
| 内联类 | 类型安全的类型别名 | 有类型检查,无性能损耗 |