17.Kotlin 类:类的形态(四):枚举类 (Enum Class)

希望帮你在Kotlin进阶路上少走弯路,在技术上稳步提升。当然,由于个人知识储备有限,笔记中难免存在疏漏或表述不当的地方,也非常欢迎大家提出宝贵意见,一起交流进步。 ------ Android_小雨

整体目录:Kotlin 进阶不迷路:41 个核心知识点,构建完整知识体系

一、前言

Kotlin 官方对枚举类的完整定义 Widest spread(逐句拆解)

Enum classes in Kotlin are declared using the enum class keyword. Each enum constant is an object (enum constants are separated by commas). Enum constants can have properties and functions just like normal objects. The compiler automatically generates:

  • values() -- returns an array of all enum entries
  • valueOf(name) -- returns the enum constant by name (throws IllegalArgumentException if not found)
  • entries (Kotlin 1.9+) -- type-safe EnumEntries list (preferred over values())

Enums in Kotlin are classes, so they can implement interfaces, have constructors, and even have different properties per constant.

官方强调的 5 个核心点(记住这 5 条就彻底掌握枚举类):

  1. 每个枚举常量都是一个对象(它们是通过 Java 的 static final 字段来引用的,但它们不是简单的 int/String 基本类型常量)。
  2. 可以给每个常量单独传参、实现不同逻辑
  3. 自动生成 values() / valueOf() / entries
  4. 支持接口实现、匿名类重写方法
  5. Kotlin 1.9+ 推荐使用 entries(类型安全的集合)

在 Kotlin 中,不要把枚举看作一组'常数',要把它们看作一组'预定义好的单例对象集合'。

1.1 枚举类的核心定位

在日常开发中,我们总会遇到一些"固定且有限"的集合:一周有七天、方向有东南西北、HTTP 状态码有 200/404/500。 如果用 intString 常量来表示(比如 public static final int NORTH = 1),代码中就会充斥着"魔法数字",既不直观也不安全。

枚举类 (Enum Class) 的核心定位就是:定义固定有限的常量集合,保障类型安全。它强迫你在编译期就确定好所有的可能性,把运行时可能出现的"无效值"错误扼杀在摇篮里。

1.2 Kotlin 枚举类的设计优势

相比于 Java,Kotlin 的枚举虽然底层一致,但语法更加简洁,且自带了现代化的工具方法。 它是**"带超能力的枚举"**:

  • 不仅是常量:每个枚举项本质上都是一个完整的对象。
  • 功能丰富:支持属性、方法、接口实现,甚至匿名类重写。
  • 工具完善 :Kotlin 1.9+ 引入的 entries 属性,让遍历枚举变得无需对象分配且类型安全。

1.3 核心疑问:Kotlin 枚举类与 Java 枚举有何关联?

很多从 Java 转过来的同学会问:enum class 到底是个什么新物种? 答案是:它不是新物种 。Kotlin 枚举在 JVM 层面上,完全等同于 Java 的 enum。这意味着它们在字节码层面是一致的,互操作性 100%。

1.4 本文核心内容预告

本文将从底层开始,层层递进:

  1. Java 映射 :看透它的底层实现(继承自 java.lang.Enum)。
  2. 基础语法:掌握 2025 年最新的定义方式。
  3. 核心特性:理解类型安全与单例本质。
  4. 实战场景:状态机、策略模式等真实案例。
  5. 对比分析:彻底搞懂它和密封类(Sealed Class)的区别。

二、核心揭秘:Kotlin 枚举类 → Java 代码映射

要理解枚举的本质,必须看它编译后的样子。

2.1 映射底层原理

Kotlin 的 enum class 在编译后,会生成一个继承自 java.lang.Enum 的类。

  • 每个枚举常量,都会变成该类的一个 public static final 实例。
  • 这意味着枚举常量在 JVM 中是单例的。

2.2 完整示例映射

2.2.1 基础枚举类

Kotlin 原代码:

kotlin 复制代码
enum class Direction {
    NORTH, SOUTH
}

Java 反编译结果(简化版):

java 复制代码
public final enum Direction extends Enum<Direction> {
    public static final Direction NORTH;
    public static final Direction SOUTH;

    // 静态初始化块,创建单例
    static {
        NORTH = new Direction("NORTH", 0);
        SOUTH = new Direction("SOUTH", 1);
    }
}

2.2.2 带属性/方法的枚举类

Kotlin 原代码:

kotlin 复制代码
enum class HttpStatus(val code: Int) {
    OK(200);
    fun isSuccess() = code == 200
}

Java 反编译结果:

java 复制代码
public final enum HttpStatus extends Enum<HttpStatus> {
    public static final HttpStatus OK;
    private final int code; // 属性变成了 final 字段

    // 构造函数私有化
    private HttpStatus(String name, int ordinal, int code) {
        super(name, ordinal);
        this.code = code;
    }

    // 方法变成普通成员方法
    public final boolean isSuccess() {
        return this.code == 200;
    }
}

2.3 关键细节

  1. 构造函数 :JVM 自动处理了 nameordinal 参数,我们定义的参数(如 code)被追加在后面。
  2. values() / valueOf() :这些静态方法是由编译器自动合成生成的(Synthesized),用于查找和遍历。

三、枚举类基础:定义与语法规范

3.1 基本语法

使用 enum class 关键字(注意是两个词,中间有空格)。

3.2 基础枚举定义

适用于不需要携带额外数据的场景。

kotlin 复制代码
enum class Gender {
    MALE, FEMALE, OTHER
}

3.3 带属性的枚举类

这是枚举最常用的形态。通过主构造函数定义属性,每个常量必须传入对应的值。

kotlin 复制代码
enum class Color(val hex: Int, val description: String) {
    RED(0xFF0000, "红色"),
    GREEN(0x00FF00, "绿色"),
    BLUE(0x0000FF, "蓝色"); // 注意:如果有额外成员,最后一个常量后必须加分号

    val rgbString: String
        get() = "#" + Integer.toHexString(hex)
}

3.4 带方法的枚举类(匿名类实现)

每个枚举常量不仅可以有属性,还可以有不同的行为 。这实际上是策略模式的一种微型实现。

kotlin 复制代码
enum class Operator {
    PLUS {
        override fun apply(x: Int, y: Int) = x + y
    },
    MINUS {
        override fun apply(x: Int, y: Int) = x - y
    }; // 分号必须

    // 定义抽象方法,强制每个常量实现
    abstract fun apply(x: Int, y: Int): Int
}

3.5 简单示例

定义一个支付方式枚举:

scss 复制代码
enum class PayType(val typeId: Int) {
    ALIPAY(1),
    WECHAT(2),
    CREDIT_CARD(3)
}

四、枚举类的核心特性

4.1 类型安全

这是枚举存在的最大意义。 fun pay(type: PayType) 当你看到这个函数签名时,你很放心,因为调用者绝对不可能传入一个非法的类型(比如传入 4null)。编译器会帮你看住大门。

4.2 单例特性

每个枚举常量(如 PayType.ALIPAY)在整个应用生命周期中只有一个实例 。你可以直接用 ===== 来比较它们,效果完全一样且高效。

4.3 自带工具方法

Kotlin 编译器自动生成了以下神器:

  • entries (Kotlin 1.9+ 推荐) :返回一个类型安全的、不可变的 List,包含所有常量。

    • 优势 :相比旧的 values()entries 避免了每次调用都克隆一个新数组,性能更好,且支持集合操作。
  • values() (旧版,不推荐) :返回一个数组 Array

  • valueOf(name: String) :通过名字查找常量。如果找不到会抛 IllegalArgumentException

  • name: 常量的名称(String)。

  • ordinal: 常量的索引(Int,从 0 开始)。

go 复制代码
// 遍历 (推荐写法)
PayType.entries.forEach { println(it.name) }

// 查找
val type = PayType.valueOf("ALIPAY")

4.4 支持属性与方法

枚举不是死板的常量,它是类。你可以给它加 fun isRecommended(),或者 val label: String,让枚举自己携带业务逻辑,避免到处写 if (type == ALIPAY) ...

4.5 可实现接口

枚举可以实现接口,这让它能融入更复杂的架构中。

kotlin 复制代码
interface Printable { fun print() }

enum class Level : Printable {
    LOW { override fun print() = println("Low") },
    HIGH { override fun print() = println("High") }
}

五、枚举类的使用场景与实战

5.1 固定选项场景

如下拉框选项、性别、血型。

kotlin 复制代码
enum class BloodType { A, B, AB, O }

5.2 状态标识场景

网络请求状态、订单流转状态。

kotlin 复制代码
enum class RequestState {
    IDLE, LOADING, SUCCESS, ERROR
}

5.3 与 when 表达式适配(穷尽检查)

这是枚举最爽的特性。配合 when 表达式,编译器会强制你处理所有情况。

scss 复制代码
fun handleState(state: RequestState) {
    when (state) {
        RequestState.IDLE -> init()
        RequestState.LOADING -> showProgress()
        RequestState.SUCCESS -> showData()
        RequestState.ERROR -> showError()
    }
    // 不需要 else!如果你漏了一个状态,编译器会报错。
}

5.4 完整实战示例

一个带有逻辑判断的权限枚举:

kotlin 复制代码
enum class Permission(val description: String) {
    READ_CONTACTS("读取联系人"),
    CAMERA("使用相机"),
    LOCATION("获取定位");

    // 模拟检查权限的逻辑
    fun isSensitive(): Boolean {
        return this == LOCATION || this == READ_CONTACTS
    }
}

fun request(p: Permission) {
    if (p.isSensitive()) {
        println("正在申请敏感权限: ${p.description}")
    }
}

六、枚举类与密封类 (Sealed Class) 的核心区别

这是面试和架构选型的高频问题。

6.1 实例特性

  • 枚举 (Enum)单例的State.ERROR 全局只有一个对象。你不能创建 State.ERROR("Network error")State.ERROR("Timeout") 两个不同的错误对象。
  • 密封类 (Sealed Class)子类可以多实例 。你可以创建无数个 Error 对象,每个携带不同的异常信息。

6.2 数据灵活性

  • 枚举 :所有常量的结构必须完全一致(都由主构造函数决定)。
  • 密封类 :子类可以各玩各的。一个是 object,一个是 data class,结构互不干扰。

6.3 适用场景对比表

特性 枚举类 (Enum) 密封类 (Sealed Class)
状态数量 固定有限 固定有限(类型有限)
数据载荷 必须相同(如都有 code, msg) 可以不同(A 带 String, B 带 Exception)
实例数量 单例 (Singleton) 多实例 (Instance)
典型场景 星期、颜色、固定配置 UI 状态(需带数据)、网络结果

结论 :如果你的状态不需要携带动态数据(或者所有状态携带的数据结构完全一样),优先用 枚举 。如果不同状态需要携带不同数据(比如 Success 带 data,Error 带 exception),必须用 密封类

七、枚举类的进阶用法

7.1 枚举常量实现抽象方法

在 3.4 节已展示。这种写法非常适合策略模式 ,比如计算器应用,加减乘除逻辑不同,但都属于 Operation 类型。

7.2 枚举类结合伴生对象 (Companion Object)

通常用于编写"反向查找"逻辑,比如根据 code 找枚举。

kotlin 复制代码
enum class ErrorCode(val code: Int) {
    NOT_FOUND(404), SERVER_ERROR(500);

    companion object {
        fun fromCode(code: Int): ErrorCode? {
            // entries 是 Kotlin 1.9+ 的高效写法
            return entries.find { it.code == code }
        }
    }
}

7.3 枚举类用于序列化

在 Gson 或 Moshi 等库中,枚举通常会被自动序列化为它的 name 字符串。但如果你想序列化为 code (Int),通常需要配合 @SerializedName (Gson) 或 @Json (Moshi) 注解。

7.4 枚举类存储配置信息

有时候可以用枚举来替代复杂的配置类:

kotlin 复制代码
enum class ThemeConfig(val colorPrimary: Int, val isDark: Boolean) {
    LIGHT(0xFFFFFF, false),
    DARK(0x000000, true)
}

八、使用注意事项与避坑点

8.1 性能提示

枚举在 Java/Kotlin 中是完整的对象,比 static final int 常量占用更多的内存(每个实例大概多几十字节)。

  • Android 开发注意 :虽然现在设备性能好了,但如果你有成千上万个状态需要保存,考虑使用 @IntDef (Android 注解) 来替代枚举以节省内存。但在 99% 的业务逻辑中,枚举的内存开销可忽略不计,优先换取代码可读性和安全性

8.2 不可动态扩展

枚举是编译期常量。你不能在运行时从服务器下载一个 JSON,然后动态往枚举类里添加一个新类型。如果你的类型列表是动态变化的(比如后台配置的活动类型),请使用普通的 data class 或数据库表。

8.3 valueOf() 风险

enumValueOf<T>("NAME")Enum.valueOf("NAME") 是不安全的。 如果传入的字符串不匹配任何枚举名,应用会崩溃 (抛出异常)。 避坑 :自己封装一个 safeValueOf,或者利用 entries.find { ... } 来安全查找。

8.4 永远不要依赖 ordinal

ordinal 表示枚举声明的顺序(0, 1, 2...)。 千万不要ordinal 存入数据库!因为如果你哪天心情好,在中间插入了一个新枚举,后面所有枚举的 ordinal 都会变,导致数据库数据错乱。始终使用自定义的 idcode 属性。

九、总结与最佳实践

9.1 核心知识点回顾

  • 本质class,继承自 java.lang.Enum,单例对象。
  • 语法enum class
  • 工具 :优先使用 entries (1.9+),慎用 valueOf
  • 穷尽 :配合 when 表达式,不写 else

9.2 最佳实践

  1. 优先用枚举 :凡是固定的常量集合,不要用 Interface 定义常量,全部上枚举。
  2. 自定义属性 :给枚举加上 codedesc 等属性,让它成为"富模型",而不是单纯的符号。
  3. 伴生对象工厂 :提供 fromCode() 等静态方法,方便业务层调用。
  4. 去魔法化 :代码中不应出现 type == 1,而应是 type == UserType.VIP

9.3 选型建议

  • 无动态数据 + 类型有限 (如性别、星期) → 枚举 (Enum)
  • 带动态数据 + 类型有限 (如 Result.Success(data)) → 密封类 (Sealed Class)
  • 类型无限普通类 / 接口

掌握枚举类,是告别"面条代码"和"魔法数字"的第一步。用好它,你的 Kotlin 代码将变得既严谨又优雅。

相关推荐
h***34631 小时前
MS SQL Server 实战 排查多列之间的值是否重复
android·前端·后端
用户69371750013841 小时前
16.Kotlin 类:类的形态(三):密封类 (Sealed Class)
android·后端·kotlin
马卡巴卡1 小时前
MySQL权限管理的坑你踩了没有?
后端
4***17541 小时前
Spring Boot整合WebSocket
spring boot·后端·websocket
Penge6661 小时前
Elasticsearch 集群必看:为什么 3 个 Master 节点是生产环境的 “黄金配置”?
后端
Java水解1 小时前
MyBatis 源码深度解析:从 Spring Boot 实战到底层原理
后端·mybatis
随风飘的云2 小时前
es搜索引擎的持久化机制原理
后端
Se7en25812 小时前
基于 MateChat 构建 AI 编程智能助手的落地实践
后端
n***F8752 小时前
Skywalking介绍,Skywalking 9.4 安装,SpringBoot集成Skywalking
spring boot·后端·skywalking