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

我似乎多次遇到这个问题,我想问一下社区是否只是在咆哮错误的树。 基本上我的问题可以归结为:如果我有一个值很重要的枚举(在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; } 

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

 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 extends java.util.HashMap { private java.util.HashMap reverseMap; public ReversibleHashMap() { super(); reverseMap = new java.util.HashMap(); } @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 monthNumMap; static { monthNumMap = new ReversibleHashMap(); 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 numberToMonthMap; private final int monthNum; static { numberToMonthMap = new HashMap(); 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()的返回值以防止创建一个每次通话都有新arrays。 (正如cletus所说, getMonthNum可以返回ordinal() + 1

但是,在更一般的情况下,值可能会出现故障或稀疏分布,值得了解上述模式。

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

 numberToMonthMap.put(monthNum, this); 

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

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

 public enum Month { JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER; public Month previous() { int prev = ordinal() - 1; if (prev < 0) { prev += values().length; } return values()[prev]; } public Month next() { int next = ordinal() + 1; if (next >= values().length) { next = 0; } return values()[next]; } } 

至于如何将其存储在数据库中,它取决于您正在使用的持久性框架(如果有)。 JPA / Hibernate可以选择使用数字(序号)或名称来映射枚举值。 几个月你可以采取不变的方式,所以只需使用序数。 要获得特定值:

 Month.values()[ordinalNumber]; 

我可能在这里的答案中远远落后于我,但我倾向于实现它更简单一些。 不要忘记’Enum’有一个values()方法。

 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 } 

你不应该对这种事情使用ordinal(),因为它可以使用几个月的样本(因为它不会被扩展)但是java中枚举的一个好处是它们的设计可以扩展没有破坏事物。 如果你开始依赖ordinal(),如果在中间添加一些值,事情就会破裂。

我会像Jon Skeet建议的那样(他在写这篇文章时写的)但是对于内部数字表示在0到20(或者其他)的明确定义范围内的情况,我可能不会使用HashMap并介绍int的自动装箱,而是使用普通的数组(如月[12]),但两者都很好(Jon后来改变了他的post以包含这个建议)。

编辑:对于少数具有自然顺序的枚举(如排序的月份),ordinal()可能是安全的。 如果你坚持它可能会遇到的问题会出现在有人可能改变枚举顺序的事情上。 就像这样:“enum {MALE,FEMALE}”变成了一个“enum {UNKNOWN,FEMALE,MALE}”,当有人在将来扩展程序时不知道你依赖序数。

给Jon写一个+1写作同样我只是写作。