枚举转换的四种写法
前言:
最近开发任务不重,在完成任务工作后,不由的思考起了各种写法的效率和适用场景。
比如下面这种情况
数据库查出来的executionStatus、executionResult我需要转换成executionStatusCH; (执行状态中文描述)、 executionResultCH; (执行结果中文描述)
实现
在创建完两个枚举类:ExecutionStatusEnum ExecutionResultEnum后,有大概一下四种转换的写法。让我们一次介绍并且分析写法的效能和适用场景。
1. Lambda 表达式(推荐)
1 | vos.forEach(vo -> { |
- 优点:简洁、可读性强,符合现代 Java 风格。
- 效率:略高,因为无额外对象创建,直接执行。
2. for-each 循环(传统方式)
1 | for (MegrezDataValidJobDetailsVo vo : vos) { |
- 优点:兼容性好,适合复杂逻辑。
- 效率:与 Lambda 几乎一致,JVM 会优化为相同字节码。
3. Stream + map(不推荐用于此场景)
1 | return vos.stream() |
- 优点:函数式编程风格。
- 缺点:性能较差,涉及中间流和收集操作,增加内存开销。
- 效率:最低,不适合批量数据处理。
4. 提前构建映射表(适用于大数据量)
1 | // 在类加载时预初始化映射 |
- 优点:避免每次调用枚举方法,减少反射开销。
- 效率:最高,尤其在大量数据下表现优异。
- 适用场景:高并发或大数据量查询。
效率对比总结
| 写法 | 性能 | 推荐度 |
|---|---|---|
| Lambda 表达式 | ⭐⭐⭐⭐☆ | 推荐 |
| for-each 循环 | ⭐⭐⭐⭐☆ | 推荐 |
| Stream + map | ⭐⭐☆☆☆ | 不推荐 |
| 预构建映射表 | ⭐⭐⭐⭐⭐ | 强烈推荐(大数据量) |
💡 建议:
- 若数据量较小(<1000),使用
Lambda或for-each即可;- 若数据量大或频繁调用,建议使用 预构建映射表 方式。
深度分析
Lambda表达式 (
forEach)
Lambda表达式vo -> {...}是一个Consumer接口的实例。在字节码层面,它使用invokedynamic指令进行动态绑定,JVM会为其生成一个内部类。当你调用forEach方法时,本质上是遍历集合,并对每个元素调用Consumer.accept(T)方法。这个过程中,可能会生成一个新的Consumer实例,不过在非捕获场景下(即Lambda不引用外部局部变量),JVM可能会缓存该实例以降低开销。它的性能介于传统循环和Stream之间。for-each循环
这是一种语法糖。编译器会将其转换为使用Iterator的标准代码。它显式地调用集合的iterator()方法,并在循环中通过Iterator的hasNext()和next()方法遍历。这种方式在字节码层面是直接的invokeinterface指令,JVM可以很容易地对其进行优化(如方法内联),因此效率很高,是通用性和性能的良好平衡点。Stream
map操作
这是开销最大的方式,原因在于其设计机制:- 流水线机制:
stream().map().collect()会构建一个完整的Stream流水线。这涉及到创建多个中间对象,如Head(流头部)、StatelessOp(无状态中间操作,如map)等,这些都属于ReferencePipeline类型。大量临时对象的创建和销毁会带来内存压力。 - 执行过程复杂:流的执行需要通过一个
Sink链来传递元素。每个元素都要经过一系列begin() -> accept() -> end()的方法调用,这比直接的循环或forEach产生了更多的抽象层和方法调用开销。 - 终端操作开销:
collect(Collectors.toList())本身也是一个复杂的操作,需要初始化一个新的ArrayList,并可能涉及多次扩容和数据拷贝。
- 流水线机制:
预构建映射表
这种方式性能最优,因为它从根本上改变了操作的性质:- 空间换时间:通过预计算,将可能的枚举查询结果缓存到
Map中。后续每次查询都是哈希表的O(1)操作,这比每次调用枚举方法(可能涉及数组遍历或开关判断)要快得多。 - 消除重复计算:在大数据量下,避免了为每个元素重复执行
ExecutionStatusEnum.getDescByCode()和ExecutionResultEnum.getDescByCode()方法的开销。特别是如果这些方法内部有复杂逻辑(如循环查找),性能提升会非常显著。
- 空间换时间:通过预计算,将可能的枚举查询结果缓存到
