What's the best way to implement `next` and `previous` on an enum type?
假设我有一个枚举:
如lucasmo在此答案中所示,枚举值按初始化顺序存储在静态数组中,以后您可以使用E.values()检索此数组(的克隆)。
现在,假设我要实现E#getNext和E#getPrevious,以使以下所有表达式求和为true:
1 2 3 4 5 6 7
| E.A.getNext() == E.B
E.B.getNext() == E.C
E.C.getNext() == E.A
E.A.getPrevious() == E.C
E.B.getPrevious() == E.A
E.C.getPrevious() == E.B |
我当前对getNext的实现如下:
1 2 3 4 5 6 7 8 9
| public E getNext() {
E[] e = E.values();
int i = 0;
for (; e[i] != this; i++)
;
i++;
i %= e.length;
return e[i];
} |
和getPrevious的类似方法。
但是,此代码充其量似乎很麻烦(例如,"空" for循环,可争论的滥用计数器变量,最坏的情况下可能是错误的(可能是反射)。
在Java 7中为枚举类型实现getNext和getPrevious方法的最佳方法是什么?
注意:我不希望这个问题是主观的。 我对"最佳"实施的要求是要求最快,最清洁和最可维护的实施的简写。
-
E.C.getPrevious() == E.C或E.C.getPrevious() == E.B?
-
@ johnchen902已修复; 抱歉
尝试这个:
1 2 3 4 5 6 7
| public static enum A {
X, Y, Z;
private static A[] vals = values();
public A next()
{
return vals[(this.ordinal()+1) % vals.length];
} |
previous()的实现留作练习,但是请记住,在Java中,模a % b可以返回负数。
编辑:根据建议,制作values()数组的私有静态副本,以避免每次调用next()或previous()时复制数组。
-
啊!我不知道ordinal;我寻找了indexOf。声明A[] values = values()以避免克隆两次会更好吗?
-
由于枚举值是单例,因此如果涉及克隆,我会感到惊讶。您总是可以查看生成的代码。
-
values方法返回(A[]) ($VALUES.clone()),其中private static final A[] $VALUES = new A[] {X,Y,Z}。看到这个答案。
-
Singleton何时可以执行A.values()[0] = null或A.values()[0] = A.Z并弄乱其他所有内容都没有关系。仅仅因为数组是最终数组并不意味着其内容就是。
-
@WChargin您甚至可以将values字段设为静态,因为对于所有枚举值而言,它都是相同的。
-
由于其他人指出的原因,我只是反汇编了代码并发现它确实可以克隆。因此,我只需复制一次即可使用。
-
顺便说一句,我用Eclipse编译了代码,并用javap进行了反汇编。它不会克隆,但new然后是System.arraycopy。
-
我很想在什么时候可以声明private static final A[] vals并去城镇时就声明private static final A[] vals,据我所知,假设类加载器/ VM没有受到损害,方法内部的反射是无能为力的。
-
@ johnchen902无论如何都要重新分配内存。该方法并不真正重要(尽管arraycopy可能会更快一些)。
-
好吧,问题是你想变得多么偏执...你的目标是什么?
-
@WChargin如果您担心这个问题,那么非静态方法也不会帮助您。有人可以通过将E.A(或任何其他枚举值)而不是null传递给Field设置器的第一个参数来到达城镇。如果您担心以这种方式进行反射,则每次都需要对克隆执行性能"命中"(我怀疑这将是非常重要的,除非这是非常紧密的循环)。
-
如果某人有权访问运行Java代码的计算机,则以任何方式对其进行切片都会损害安全性;他们只需注入javaagent并将您的字节码重写为所需的任何内容即可。如果他们没有直接访问权限,但是您允许使用任意插件,那么您无论如何肯定需要某种安全管理器。另外,我会质疑将不受信任的第三方插件加载到服务器端应用程序的选择。因此,基本上,从反射的角度来看,我不会太担心安全性,因为您基本上没有任何安全性。 :)
-
如果您有一个名为" a"的" A"实例,例如:A a。然后像这样分配它:a = a.next();调用a.next();不会自己增加。请参阅stackoverflow.com/questions/17664445/java-operator-for-enum中的波西米亚人答案
或者,可以按照以下思路进行某种方式的选择:
1 2 3 4 5 6 7 8 9 10 11
| public enum SomeEnum {
A, B, C;
public Optional<SomeEnum> next() {
switch (this) {
case A: return Optional.of(B);
case B: return Optional.of(C);
// any other case can NOT be mapped!
default: return Optional.empty();
}
} |
笔记:
与其他答案相反,这种方式进行了一些隐式映射。而不是依赖ordinal()。当然,这意味着需要更多代码。但它也迫使作者考虑添加新常量或删除现有常量的含义。当依赖于序数时,隐式假设是该顺序基于用于枚举常量声明的顺序。因此,当某人六个月后回来并必须添加一个新常量时,他必须了解新常量Y需要X, Y, Z ...而不是仅追加X, Z, Y!
在某些情况下,将"最后一个"枚举常量作为"后继"枚举没有任何意义。以T恤尺寸为例。 XXL.next()肯定不是XS。对于这种情况,使用Optional是更合适的答案。
-
嗨,GhostCat。我想我没有在问题中明确指出这一点,但我希望在默认顺序是逻辑上的前提下,寻求一种可自动扩展的解决方案(即,不是"强力"解决方案)。我真的不认为这可以回答问题,但是您认为不希望使用默认排序的观点确实很有价值。
-
别客气;我只是碰到了其他一些问题和想法:尤其是可选部分将值得自己回答。
-
是啊。我喜欢Optional可能是什么,并且在大型项目中给予了很好的评价。太糟糕了,它无法很好地集成到语言的其余部分中。