枚举是 [JVM@](
[email protected]) 里一张不会变的参考表,就好像 [汉谟拉比法典](汉谟拉比法典)
所以枚举的 field 应该是 final 的,不然会出这种警告 `Non-final field 'code' in enum 'HomogenizationSelectorType'`
## 反编译后的枚举代码
values() 方法是 Java 编译器在编译时自动为每个枚举类合成的静态方法。当我们定义一个枚举类时,编译器会自动添加这些特殊的方法。
让我来详细解释一下:
1. 编译器合成的方法:
```java
// 这些方法在编译后会被自动添加到枚举类中
public static BedroomCountEnum[] values();
public static BedroomCountEnum valueOf(String name);
```
1. 反编译一个枚举类看看实际实现:
```java
// 原始代码
public enum BedroomCountEnum {
ONE(1, "1间卧室"),
TWO(2, "2间卧室");
private final int code;
private final String desc;
BedroomCountEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
}
// 反编译后的主要部分
public final class BedroomCountEnum extends Enum<BedroomCountEnum> {
// 1. 每个枚举常量都被转换为静态final字段
public static final BedroomCountEnum ONE = new BedroomCountEnum("ONE", 0, 1, "1间卧室");
public static final BedroomCountEnum TWO = new BedroomCountEnum("TWO", 1, 2, "2间卧室");
private final int code;
private final String desc;
// 2. 编译器生成的values()方法实现
private static final BedroomCountEnum[] $VALUES;
static {
$VALUES = new BedroomCountEnum[]{ONE, TWO};
}
public static BedroomCountEnum[] values() {
return (BedroomCountEnum[])$VALUES.clone();
}
// 3. 编译器生成的valueOf()方法实现
public static BedroomCountEnum valueOf(String name) {
return (BedroomCountEnum)Enum.valueOf(BedroomCountEnum.class, name);
}
// 构造方法
private BedroomCountEnum(String name, int ordinal, int code, String desc) {
super(name, ordinal);
this.code = code;
this.desc = desc;
}
}
```
关键点解释:
1. values() 方法的实现:
- 编译器会创建一个私有的静态数组 $VALUES 存储所有枚举常量
- values() 方法返回这个数组的克隆,防止外部修改原数组
- 返回的数组顺序与枚举常量定义顺序一致
2. 性能考虑:
```java
// 每次调用values()都会克隆数组
BedroomCountEnum[] values = BedroomCountEnum.values(); // 会创建新数组
// 如果需要频繁使用,最好缓存结果
public class BedroomCountEnumUtils {
private static final BedroomCountEnum[] VALUES = BedroomCountEnum.values();
public static BedroomCountEnum[] getValues() {
return VALUES; // 直接返回缓存的数组
}
}
```
1. 为什么要克隆数组?
```java
// 如果直接返回原数组,可能会被修改
BedroomCountEnum[] values = BedroomCountEnum.values();
values[0] = null; // 如果不是克隆的数组,这会影响原始数据
// 克隆确保了枚举值的不可变性
BedroomCountEnum[] values1 = BedroomCountEnum.values();
BedroomCountEnum[] values2 = BedroomCountEnum.values();
values1[0] = null; // 不会影响values2
```
所以,当我们使用 values() 方法时,要注意:
1. 它是编译器自动生成的
2. 每次调用都会创建新的数组(克隆)
3. 如果需要频繁使用,建议缓存结果
4. 返回顺序与定义顺序一致
5. 实际是基于一个私有的静态数组实现的
这也就解释了为什么前面讨论的遍历方式会比 Map 方式慢 - 因为每次调用 values() 都会克隆数组,而且还要遍历这个数组。相比之下,Map 的查找是 O(1) 的,且不需要克隆操作。
## 根据 code 反查 enum
这两种实现方式各有优劣,我来分析一下:
1. 遍历方式 (values()):
```java
public static BedroomCountEnum getByCode(int code) {
for (BedroomCountEnum value : values()) {
if (value.getCode() == code) {
return value;
}
}
return null;
}
```
优点:
- 代码简单直观,易于理解和维护
- 内存占用少,不需要额外的数据结构
- 适合枚举值较少的情况(一般小于10个)
- 类加载时无需额外初始化
缺劣:
- 时间复杂度为 O(n),需要遍历所有枚举值
- 频繁调用时性能较差
1. Map缓存方式:
```java
private static final Map<Integer, BedroomCountEnum> CODE_MAP;
static {
CODE_MAP = Maps.newHashMap();
for (BedroomCountEnum value : values()) {
CODE_MAP.put(value.getCode(), value);
}
}
public static BedroomCountEnum getByCode(int code) {
return CODE_MAP.get(code);
}
```
优点:
- 查找时间复杂度为 O(1),性能好
- 适合频繁查询的场景
- 适合枚举值较多的情况
缺点:
- 需要额外的内存存储 Map
- 类加载时需要初始化 Map
- 代码稍微复杂一些
选择建议:
1. 使用遍历方式的场景:
- 枚举值较少(比如少于10个)
- 查询频率不高
- 内存资源紧张
- 追求代码简洁性
1. 使用 Map 缓存的场景:
- 枚举值较多
- 查询频繁
- 对查询性能要求高
- 内存资源充足
在你的例子中,因为是房间数量枚举,通常不会有太多值,所以两种实现方式差异不大。但如果这个方法会被频繁调用(比如在循环中或批量处理数据时),建议使用 Map 方式。
补充一个性能对比的简单示例:
```java
// 假设有1000000次查询
@Test
public void performanceTest() {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
BedroomCountEnum.getByCode(2); // 遍历方式
}
long end1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
BedroomCountEnum.codeOf(2); // Map方式
}
long end2 = System.currentTimeMillis();
System.out.println("遍历方式耗时:" + (end1 - start) + "ms");
System.out.println("Map方式耗时:" + (end2 - end1) + "ms");
}
```
实际项目中,如果这个枚举类使用频率高,建议使用 Map 方式。如果使用频率低,两种方式都可以,选择你觉得更容易维护的方式即可。