通过添加 Enum 静态 Holder 进行优化

Optimize by adding Enum a static Holder

我想知道添加一个 Enum 静态 Holder 是否总是比在“get”枚举方法(或类似的获取特定枚举值)上迭代值更好的实现。

例如 Spring HttpStatus 当前实现:

HttpStatus(int value, String reasonPhrase) {
    this.value = value;
    this.reasonPhrase = reasonPhrase;
}
public static HttpStatus valueOf(int statusCode) {
    for (HttpStatus status : values()) {
        if (status.value == statusCode) {
            return status;
        }
    }
    throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
}

可以优化为:

private static class Holder {
    private static Map<Integer, HttpStatus> enumMap = new HashMap<>();
}

HttpStatus(int value, String reasonPhrase) {
    this.value = value;
    this.reasonPhrase = reasonPhrase;
    Holder.enumMap.put(value, this);
}

public static HttpStatus valueOf(int statusCode) {
     return Holder.enumMap.computeIfAbsent(statusCode, statusCode -> {
            throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); });
}

代码优化:

循环版本具有线性时间复杂度(每次请求获取值),而使用 HashMap 的版本具有 O(1) 的时间复杂度。

此优化是否存在我遗漏的缺陷?

One thing to bear in mind is that, once initialized, the static map will be held in memory indefinitely; the loop approach will only hold the array created by calling values() for the duration of the loop iteration.

However, it is worth pointing out that there is no advantage in using a holder class: because you are adding to the map in the constructor, the holder class will be initialized straight away when the constructor is invoked.

As such, you may as well use a plain old static (final) field.

It is also worth considering that this approach necessarily requires the map to be mutable. You may not intend to mutate it, but it is worth changing the approach defensively so that you can't.

Instead, you can initialize directly on the field:

static final Map<Integer, HttpStatus> map = 
    Collections.unmodifiableMap(
        Stream.of(HttpStatus.values())
            .collect(toMap(s -> s.value,  s -> s)));

(Or use something like a Guava ImmutableMap)

Another point about the approach of initializing the map directly is that it doesn't add to the map in the constructor - which means that you don't actually have to put this into the enum class itself. This gives you the flexibility to use it on enums where you don't have the ability to change the code, and/or to only add the map to places where you have found there is a performance need.

我很久以前检查过这种方法,我决定没有好处使用Map而不是手动循环值。

  • 映射发生在内存中
  • O(1)搜索做map确实比O(n)手动循环好,枚举应该有1000多个常量(这是实验数)。但根据我的经验,我枚举了最多 200 多个常量的国家/地区。
  • 在最坏的情况下,这也不是整个应用程序的瓶颈

我一直使用手动循环,不担心性能问题。许多序列化框架(如 Jackson 将枚举转换为 json)使用 Enum.valueOf().