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

Kid*_*rla 9 java enums idiomatic

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

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

public interface MonthAndYear {
    Month getMonth();
    void setMonth(Month month);
    int getYear();
    void setYear(int year);
}
Run Code Online (Sandbox Code Playgroud)

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

public enum Month {
    JANUARY,
    FEBRUARY,
    MARCH,
    APRIL,
    MAY,
    JUNE,
    JULY,
    AUGUST,
    SEPTEMBER,
    OCTOBER,
    NOVEMBER,
    DECEMBER;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

相当简单,但如果我想从数据库中读取这些值并编写它们会发生什么?我可以使用枚举中的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

现在这可以做我想要的一切,但它看起来仍然是错误的.人们向我建议"如果枚举具有有意义的内部值,则应该使用常量".但是,我不知道这种方法将如何给我提供我正在寻找的类型安全性.我开发的方式确实看起来过于复杂.有没有一些标准的方法来做这种事情?

PS:我知道政府增加一个月的可能性......相当不太可能,但想想更大的情况 - 对于枚举有很多用途.

Jon*_*eet 11

这是一种非常常见的模式,它对于枚举很好......但它可以更简单地实现.有没有需要"可逆映射" -这需要在构造函数中的月份数是从去更好的版本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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

但是,值得在更一般的情况下意识到上述模式,其中值可能是乱序的,或者是稀疏分布的.

重要的是要注意在创建所有枚举值之后执行静态初始化程序.写作会很好

numberToMonthMap.put(monthNum, this);
Run Code Online (Sandbox Code Playgroud)

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