前言:

最近开发任务不重,在完成任务工作后,不由的思考起了各种写法的效率和适用场景。

比如下面这种情况

数据库查出来的executionStatus、executionResult我需要转换成executionStatusCH; (执行状态中文描述)、 executionResultCH; (执行结果中文描述)


实现

在创建完两个枚举类:ExecutionStatusEnum ExecutionResultEnum后,有大概一下四种转换的写法。让我们一次介绍并且分析写法的效能和适用场景。

1. Lambda 表达式(推荐)

1
2
3
4
vos.forEach(vo -> {
vo.setExecutionStatusCH(ExecutionStatusEnum.getDescByCode(vo.getExecutionStatus()));
vo.setExecutionResultCH(ExecutionResultEnum.getDescByCode(vo.getExecutionResult()));
});
  • 优点:简洁、可读性强,符合现代 Java 风格。
  • 效率:略高,因为无额外对象创建,直接执行。

2. for-each 循环(传统方式)

1
2
3
4
for (MegrezDataValidJobDetailsVo vo : vos) {
vo.setExecutionStatusCH(ExecutionStatusEnum.getDescByCode(vo.getExecutionStatus()));
vo.setExecutionResultCH(ExecutionResultEnum.getDescByCode(vo.getExecutionResult()));
}
  • 优点:兼容性好,适合复杂逻辑。
  • 效率:与 Lambda 几乎一致,JVM 会优化为相同字节码。

3. Stream + map(不推荐用于此场景)

1
2
3
4
5
6
7
return vos.stream()
.map(vo -> {
vo.setExecutionStatusCH(ExecutionStatusEnum.getDescByCode(vo.getExecutionStatus()));
vo.setExecutionResultCH(ExecutionResultEnum.getDescByCode(vo.getExecutionResult()));
return vo;
})
.collect(Collectors.toList());
  • 优点:函数式编程风格。
  • 缺点:性能较差,涉及中间流和收集操作,增加内存开销。
  • 效率最低,不适合批量数据处理。

4. 提前构建映射表(适用于大数据量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在类加载时预初始化映射
private static final Map<Integer, String> STATUS_MAP = new HashMap<>();
private static final Map<Integer, String> RESULT_MAP = new HashMap<>();

static {
for (ExecutionStatusEnum status : ExecutionStatusEnum.values()) {
STATUS_MAP.put(status.getCode(), status.getDesc());
}
for (ExecutionResultEnum result : ExecutionResultEnum.values()) {
RESULT_MAP.put(result.getCode(), result.getDesc());
}
}

// 使用时
vos.forEach(vo -> {
vo.setExecutionStatusCH(STATUS_MAP.getOrDefault(vo.getExecutionStatus(), "未知"));
vo.setExecutionResultCH(RESULT_MAP.getOrDefault(vo.getExecutionResult(), "未知"));
});
  • 优点:避免每次调用枚举方法,减少反射开销。
  • 效率最高,尤其在大量数据下表现优异。
  • 适用场景:高并发或大数据量查询。

效率对比总结

写法 性能 推荐度
Lambda 表达式 ⭐⭐⭐⭐☆ 推荐
for-each 循环 ⭐⭐⭐⭐☆ 推荐
Stream + map ⭐⭐☆☆☆ 不推荐
预构建映射表 ⭐⭐⭐⭐⭐ 强烈推荐(大数据量)

💡 建议:

  • 若数据量较小(<1000),使用 Lambdafor-each 即可;
  • 若数据量大或频繁调用,建议使用 预构建映射表 方式。

深度分析

  1. Lambda表达式 (forEach)
    Lambda表达式 vo -> {...} 是一个Consumer接口的实例。在字节码层面,它使用 invokedynamic 指令进行动态绑定,JVM会为其生成一个内部类。当你调用 forEach 方法时,本质上是遍历集合,并对每个元素调用 Consumer.accept(T) 方法。这个过程中,可能会生成一个新的Consumer实例,不过在非捕获场景下(即Lambda不引用外部局部变量),JVM可能会缓存该实例以降低开销。它的性能介于传统循环和Stream之间。

  2. for-each循环
    这是一种语法糖。编译器会将其转换为使用 Iterator 的标准代码。它显式地调用集合的 iterator() 方法,并在循环中通过 IteratorhasNext()next() 方法遍历。这种方式在字节码层面是直接的 invokeinterface 指令,JVM可以很容易地对其进行优化(如方法内联),因此效率很高,是通用性和性能的良好平衡点。

  3. Stream map 操作
    这是开销最大的方式,原因在于其设计机制:

    • 流水线机制stream().map().collect() 会构建一个完整的Stream流水线。这涉及到创建多个中间对象,如 Head(流头部)、StatelessOp(无状态中间操作,如map)等,这些都属于ReferencePipeline类型。大量临时对象的创建和销毁会带来内存压力。
    • 执行过程复杂:流的执行需要通过一个 Sink 链来传递元素。每个元素都要经过一系列 begin() -> accept() -> end() 的方法调用,这比直接的循环或 forEach 产生了更多的抽象层和方法调用开销。
    • 终端操作开销collect(Collectors.toList()) 本身也是一个复杂的操作,需要初始化一个新的 ArrayList,并可能涉及多次扩容和数据拷贝。
  4. 预构建映射表
    这种方式性能最优,因为它从根本上改变了操作的性质:

    • 空间换时间:通过预计算,将可能的枚举查询结果缓存到 Map 中。后续每次查询都是哈希表的 O(1) 操作,这比每次调用枚举方法(可能涉及数组遍历或开关判断)要快得多。
    • 消除重复计算:在大数据量下,避免了为每个元素重复执行 ExecutionStatusEnum.getDescByCode()ExecutionResultEnum.getDescByCode() 方法的开销。特别是如果这些方法内部有复杂逻辑(如循环查找),性能提升会非常显著。