java - 我应该尝试在 Java 中创建可逆枚举还是有更好的方法?

标签 java enums idioms

我似乎多次遇到这个问题,我想问问社区我是不是找错人了。基本上我的问题可以归结为:如果我有一个值很重要的枚举(在 Java 中),我应该使用枚举还是有更好的方法,如果我确实使用枚举那么什么是反转查找的最佳方法吗?

这是一个例子。假设我想创建一个代表特定月份和年份的 bean。我可能会创建如下内容:

public interface MonthAndYear {
    Month getMonth();
    void setMonth(Month month);
    int getYear();
    void setYear(int year);
}

在这里,我将我的月份存储为一个名为 Month 的单独类,因此它是类型安全的。如果我只输入 int,那么任何人都可以将 13 或 5,643 或 -100 作为数字传递,并且无法在编译时检查它。我限制他们输入我将作为枚举实现的月份:

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;
}

现在假设我有一些我想写入的后端数据库,它只接受整数形式。那么标准的方法似乎是:

public enum Month {
    JANUARY(1),
    FEBRUARY(2),
    MARCH(3),
    APRIL(4),
    MAY(5),
    JUNE(6),
    JULY(7),
    AUGUST(8),
    SEPTEMBER(9),
    OCTOBER(10),
    NOVEMBER(11),
    DECEMBER(12);

    private int monthNum;
    public Month(int monthNum) {
        this.monthNum = monthNum;
    }

    public getMonthNum() {
        return monthNum;
    }
}

相当简单,但如果我想从数据库中读取这些值并写入它们,会发生什么情况?我可以在枚举中使用 case 语句实现一个静态函数,该函数接受一个 int 并返回相应的 Month 对象。但这意味着如果我改变了什么,那么我将不得不改变这个函数以及构造函数参数——改变两个地方。所以这就是我一直在做的。首先,我创建了一个可逆映射类,如下所示:

public class ReversibleHashMap<K,V> extends java.util.HashMap<K,V> {
    private java.util.HashMap<V,K> reverseMap;

    public ReversibleHashMap() {
        super();
        reverseMap = new java.util.HashMap<V,K>();
    }

    @Override
    public V put(K k, V v) {
        reverseMap.put(v, k);
        return super.put(k,v);
    }

    public K reverseGet(V v) {
        return reverseMap.get(v);
    }
}

然后我在我的枚举中而不是构造函数方法中实现了它:

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;

    private static ReversibleHashMap<java.lang.Integer,Month> monthNumMap;

    static {
        monthNumMap = new ReversibleHashMap<java.lang.Integer,Month>();
        monthNumMap.put(new java.lang.Integer(1),JANUARY);
        monthNumMap.put(new java.lang.Integer(2),FEBRUARY);
        monthNumMap.put(new java.lang.Integer(3),MARCH);
        monthNumMap.put(new java.lang.Integer(4),APRIL);
        monthNumMap.put(new java.lang.Integer(5),MAY);
        monthNumMap.put(new java.lang.Integer(6),JUNE);
        monthNumMap.put(new java.lang.Integer(7),JULY);
        monthNumMap.put(new java.lang.Integer(8),AUGUST);
        monthNumMap.put(new java.lang.Integer(9),SEPTEMBER);
        monthNumMap.put(new java.lang.Integer(10),OCTOBER);
        monthNumMap.put(new java.lang.Integer(11),NOVEMBER);
        monthNumMap.put(new java.lang.Integer(12),DECEMBER);
    }

    public int getMonthNum() {
        return monthNumMap.reverseGet(this);
    }

    public static Month fromInt(int monthNum) {
        return monthNumMap.get(new java.lang.Integer(monthNum));
    }
}

现在这完成了我想要的一切,但它看起来仍然不对。人们向我建议“如果枚举具有有意义的内部值,您应该改用常量”。但是,我不知道这种方法如何为我提供我正在寻找的类型安全。不过,我开发的方式似乎过于复杂。有没有一些标准的方法来做这种事情?

PS:我知道政府增加新月份的可能性是......相当不可能,但想想更大的图景 - 枚举有很多用途。

最佳答案

这是一个非常常见的模式,它适用于枚举……但它可以更简单地实现。不需要“可逆映射”——在构造函数中采用月份数字的版本更适合从 Monthint。但走另一条路也不难:

public enum Month {
    JANUARY(1),
    FEBRUARY(2),
    MARCH(3),
    APRIL(4),
    MAY(5),
    JUNE(6),
    JULY(7),
    AUGUST(8),
    SEPTEMBER(9),
    OCTOBER(10),
    NOVEMBER(11),
    DECEMBER(12);

    private static final Map<Integer, Month> numberToMonthMap;

    private final int monthNum;

    static {
        numberToMonthMap = new HashMap<Integer, Month>();
        for (Month month : EnumSet.allOf(Month.class)) {
            numberToMonthMap.put(month.getMonthNum(), month);
        }
    }

    private Month(int monthNum) {
        this.monthNum = monthNum;
    }

    public int getMonthNum() {
        return monthNum;
    }

    public static Month fromMonthNum(int value) {
        Month ret = numberToMonthMap.get(value);
        if (ret == null) {
            throw new IllegalArgumentException(); // Or just return null
        }
        return ret;
    }
}

在您知道从 1 到 N 的数字的特定情况下,您可以简单地使用数组 - 获取 Month.values()[value - 1] 或缓存返回值Month.values() 以防止在每次调用时都创建一个新数组。 (正如 cletus 所说,getMonthNum 可以只返回 ordinal() + 1。)

但是,在值可能无序或分布稀疏的更一般情况下,值得注意上述模式。

重要的是要注意静态初始化器所有枚举值创建之后执行。最好只写

numberToMonthMap.put(monthNum, this);

在构造函数中并为 numberToMonthMap 添加一个静态变量初始值设定项,但这不起作用 - 你会立即得到一个 NullReferenceException,因为你会尝试将值放入尚不存在的 map 中:(

关于java - 我应该尝试在 Java 中创建可逆枚举还是有更好的方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1582940/

相关文章:

java - 将一些间隔图像水平添加到 Canvas 中的正确方法是什么?

java - 匹配器不返回唯一结果

python - 如何在 Django 中组合/嵌套枚举字段?

java - 枚举值获取?

python:自动区分list/tuple/array和int/float

loops - Kotlin:当条件为真时重复n次

java - 应用android.useAndroidX = true和android.enableJetifier = true修复错误会导致另一个错误

java - 将 JSONObject 写入文件

java - 是否可以强制接口(interface)仅由枚举实现?

python - Python 成语 "if __name__ == ' __main_ _'"的 clojure 等价物是什么?