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

我应该尝试用Java创建可逆枚举,还是有更好的方法?,java,enums,idioms,Java,Enums,Idioms,我似乎已经多次面对这个问题,我想问社区我是不是找错了方向。基本上,我的问题可以归结为:如果我有一个枚举(在Java中),值对它很重要,我应该使用枚举还是有更好的方法,如果我使用枚举,那么什么是反转查找的最佳方法 这里有一个例子。假设我想创建一个表示特定月份和年份的bean。我可能会创建如下内容: public interface MonthAndYear { Month getMonth(); void setMonth(Month month); int getYear

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

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

public interface MonthAndYear {
    Month getMonth();
    void setMonth(Month month);
    int getYear();
    void setYear(int year);
}
在这里,我将我的月份存储为一个单独的类,名为month,因此它是类型安全的。如果我只输入int,那么任何人都可以将13、5643或-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);
    }
}
公共类ReversibleHashMap扩展了java.util.HashMap{
私有java.util.HashMap reverseMap;
公共可逆hashmap(){
超级();
reverseMap=newjava.util.HashMap();
}
@凌驾
公共V-put(K,V-V){
反向映射put(v,k);
返回super.put(k,v);
}
公共K反转(V){
返回反向映射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));
    }
}
公共枚举月{
一月,,
二月,,
前进
四月,,
也许
六月,,
七月,,
八月,,
九月,,
十月,,
十一月,,
十二月,;
私有静态可逆hashmap monthNumMap;
静止的{
monthNumMap=新的ReversibleHashMap();
monthNumMap.put(新java.lang.Integer(1),一月);
monthNumMap.put(新java.lang.Integer(2),2月);
monthNumMap.put(新java.lang.Integer(3),三月);
monthNumMap.put(新java.lang.Integer(4),4月);
monthNumMap.put(新java.lang.Integer(5),五月);
monthNumMap.put(新java.lang.Integer(6),6月);
monthNumMap.put(新java.lang.Integer(7),7月);
monthNumMap.put(新java.lang.Integer(8),8月);
monthNumMap.put(新java.lang.Integer(9),9月);
monthNumMap.put(新java.lang.Integer(10),10月);
monthNumMap.put(新java.lang.Integer(11),11月);
monthNumMap.put(新java.lang.Integer(12),12月);
}
public int getMonthNum(){
返回月份倒转日期(本);
}
公共静态月自整数(整数月){
返回monthNumMap.get(新的java.lang.Integer(monthNum));
}
}
现在这一切我想它,但它仍然看起来是错误的。人们向我建议“如果枚举具有有意义的内部值,则应使用常量”。然而,我不知道这种方法将如何为我提供我正在寻找的类型安全性。但我的发展方式似乎过于复杂了。有没有标准的方法来做这类事情


PS:我知道政府增加一个新月份的可能性……相当不可能,但想想更大的图景——Enum有很多用途。

有一种更简单的方法可以做到这一点。每个枚举都有一个方法返回其编号(从零开始)


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

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;
    }
}
在构造函数中,为
numberToMonthMap
添加一个静态变量初始值设定项,但这不起作用-您会立即得到一个
NullReferenceException
,因为您试图将该值放入一个尚不存在的映射中:(

您不应该使用ordinal()对于这种事情,对于样本来说,它可以工作几个月(因为它不会被扩展),但是java中的枚举的一个好处是,它可以被扩展而不会破坏事物。如果你开始依赖于纵(),如果在中间增加一些值,事情就会中断。

我会按照Jon Skeet的建议去做(他在我写这篇文章的时候写的),但是对于内部数字表示在定义良好的范围内(比如0到20)的情况,我可能不会使用HashMap并引入int的自动装箱,而是使用普通数组(比如Month[12]),但两者都可以(乔恩后来更换了职位,加入了这一建议)

编辑:对于有自然顺序(如已排序的月份)的少数枚举,序数()可能可以安全使用。如果您坚持使用,您可能遇到的问题将出现在有人可能更改枚举顺序的情况下。例如: 当将来有人在不知道您依赖序号的情况下扩展程序时,“enum{MALE,FEMALE}”就变成了“enum{UNKNOWN,FEMALE,MALE}”


给Jon一个+1,因为他写的和我刚才写的一样。

我的答案可能远远落后于其他人,但我倾向于实现得简单一点。别忘了“Enum”有一个values()方法


调用
Month.values()
Month.values()[ordinalNumber];
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;
    }
}
numberToMonthMap.put(monthNum, this);
public static Month parse(int num)
{
  for(Month value : values())
  {
    if (value.monthNum == num)
    {
      return value;
    }
  }
  return null; //or throw exception if you're of that mindset
}