在Java中使用枚举作为数组索引

33

在阅读《Effective Java》时,我遇到了建议“使用枚举而不是 int 常量”。在我目前的项目中,我正在做类似于以下代码的事情:

int COL_NAME = 0;
int COL_SURNAME = 1;

table[COL_NAME] = "JONES" 

我该如何使用枚举类型来实现这个功能呢?由于必须使用我被强制使用的接口,所以我必须使用一个int作为我的索引。上面的示例只是一个示例。实际上我正在使用一个需要使用int类型索引值的API。

7个回答

51

应用一个有用的模式和一个反模式通常会失败 ;-)

在您的情况下,对于不是真正类似于数组的数据,使用数组会导致问题,当您想要将int常量替换为enum值时。

一种更为清晰的解决方案是使用EnumMap,并将enum值作为键。

或者,如果您确实必须使用,则可以使用table[COL_NAME.ordinal()]

如果某些API强制您传递int值,但您可以控制实际值(即,您可以传递自己的常量),那么您可以在代码中切换到使用enum值,并仅在代码与API接口的地方进行enum转换。enumValue.ordinal()的反向操作是EnumClass.values()[ordinal])。


我相信table [COL_NAM.ordinal()]就是EnumMap在幕后所做的事情,只不过比我们中的任何一个人在现场编写的更正确。 - ILMTitan
@ILMTitan:我同意。我刚刚发文是因为原帖作者说有些API强制要求他使用数组。 - Joachim Sauer
3
我不同意这是一种反模式。其他语言将枚举视为基元,对于基于枚举的范围、子范围和数组而言,使用枚举基元非常有意义。这比需要声明“static final int ... SOMETHING”并编写“select(...){...}”语句要更有意义(依我之见)。其次,我们需要一个“EnumMap”表明缺少某些东西。 - will
@will:这只是证明了Java中的枚举与其他语言中的枚举不同。在其他语言中,它们只是被吹嘘的整数,并且被正确地处理。但在Java中,它们并非如此。盲目地应用其他语言的模式可能会成为反模式。 - Joachim Sauer
我同意这一点,他们可能已经错误地命名了一些文档:枚举类型。虽然有人确实应该研究一下PL/1和ALGOL-68的示例。 - will

7

听起来你正在尝试使用EnumMap。它是一个包装值数组的Map。

enum Column {
   NAME, SURNAME
}

Map<Column, String> table = new EnumMap<Column, String>(Column.class);

table.put(Column.NAME, "JONES");

String name = table.get(Column.NAME);

如果你使用一个POJO,这将会简单得多。
classPerson {
   String name;
   String surname;
}

Person person = new Person();
person.name = "JONES";
String name = person.name;

2

根据需求而定。您可以使用Enum.ordinal()将枚举转换为整数。

请注意,不可能直接将枚举作为索引传递。

编辑:

另一个可能性是使用Map<YourEnum, String> map,然后使用map.get(EnumValue)


2
由于您必须使用该表格,因此无法实际使用EnumMap,我个人认为最好的解决方案是坚持现有方案。在枚举中,元素的序数值不应具有任何固有含义,而在您的情况下,它们确实具有含义,因为它们用作表格的索引。
你遇到的问题不是没有使用枚举,而是需要使用魔术值从表格中提取列数据。在这种情况下,使用整数常量是正确的工具,除非您可以解决该表格的根本问题。
现在,您可以通过将表格包装在自己的类中并在该类中使用枚举来解决它。但是,这会引入更多代码,另一层间接性,并且实际上不会为您解决任何问题,除了枚举比int值列表更容易维护(由您编写的包装器大大抵消)。
如果您正在编写其他人将使用的公共API,则可以考虑这项工作,因为这将避免他们依赖可能随时间而变化的某些魔术值(紧密耦合会破坏事物)。如果是这样,那么内部使用EnumMap的包装器很可能是正确的方法。
否则,保持原样。

1

根据您的数组值,您可能会看到一个对象。例如,如果您的数组看起来像这样

person[FIRST_NAME] = "Jim Bob"
person[SURNAME] = "Jones"
person[ADDRESS] = "123 ABC St"
person[CITY] = "Pleasantville"
...

那么你真正想要的是这样的东西

Person jimBob = new Person("Jim Bob", "Jones");
jimBob.setAddress("123 ABC St", "Pleasantville", "SC");
....

请查看重构:用对象替换数组


0

我也遇到了这个问题,来自C++世界的我没有想到会遇到这个问题。

枚举的美妙之处在于可以创建一系列相关常量,具有唯一值,实际值并不重要。它们的使用使代码更易于阅读,并防止变量被设置为无效的枚举值(特别是在Java中),但在这种情况下,它是一个主要的痛点。虽然你可能有"enum Pets { CAT, BIRD, DOG }",并不真正关心CAT、BIRD和DOG实际上代表什么值,但写起来非常简洁明了: myPets[CAT] = "Dexter"; myPets[BIRD] = "Polly"; MyPets[DOG] = "Boo-Rooh";

在我的情况下,我最终编写了一个转换函数,您可以传入一个枚举值,我返回一个常量。我非常讨厌这样做,但这是我知道的保持枚举方便性和错误检查的唯一干净方法。

private int getPetsValue(Pets inPet) {

    int value = 0; 
    switch (inPet) {

        case CAT:    value = 0;    break;
        case BIRD:   value = 1;    break;
        case DOG:    value = 2;    break;
        default:
            assert(false);
            value = 0;
            break;
    }
    return value;
}

4
我不确定你在做什么,但看起来你可能不知道Enum.ordinal()。我个人认为,最好不要像将Java当作C++那样编程,虽然两者相似但也有所不同,盲目地应用所有你习惯的方法只会导致灾难。 - maaartinus

0

您可以像这样使用构造函数定义enum

public enum ArrayIndex {
    COL_NAME(0), COL_SURNAME(1);
    private int index;

    private ArrayIndex(int index) {
    this.index = index;
    }

public int getIndex() {
    return this.index;
}
};   

然后像这样使用它:

public static void main (String args[]) {
    System.out.println("index of COL_NAME is " + ArrayIndex.COL_NAME.getIndex());
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接