如何为Java 6枚举实现values()?


72

在Java中,您可以创建一个枚举,如下所示:

public enum Letter {
    A, B, C, D, E, F, G;

    static {
       for(Letter letter : values()) {
          // do something with letter
       }
    }
}

这个问题涉及“ values()”方法。具体来说,如何实施?通常,我可以在Eclipse中使用F3或CTRL + Click跳到Java类的源代码(甚至对于String,Character,Integer甚至Enum之类的类)。可以查看其他枚举方法的源(例如valueOf(String))。

每次调用“ values()”都会创建一个新数组吗?如果将其分配给局部变量,然后修改其中一个元素,则会发生什么情况(显然,这不会影响values()返回的值,这意味着每次都会分配一个新数组)。

它的代码是本地的吗?还是JVM /编译器对它进行了特殊处理,仅当无法证明不会对其进行修改时才从values()返回一个新实例。

Answers:


112

基本上,编译器(javac)将您的枚举转换为包含在编译时所有值的静态数组。当您调用values()时,它将为您提供此数组的.clone'd()副本。

给出这个简单的枚举:

public enum Stuff {
   COW, POTATO, MOUSE;
}

您实际上可以查看Java生成的代码:

public enum Stuff extends Enum<Stuff> {
    /*public static final*/ COW /* = new Stuff("COW", 0) */,
    /*public static final*/ POTATO /* = new Stuff("POTATO", 1) */,
    /*public static final*/ MOUSE /* = new Stuff("MOUSE", 2) */;
    /*synthetic*/ private static final Stuff[] $VALUES = new Stuff[]{Stuff.COW, Stuff.POTATO, Stuff.MOUSE};

    public static Stuff[] values() {
        return (Stuff[])$VALUES.clone();
    }

    public static Stuff valueOf(String name) {
        return (Stuff)Enum.valueOf(Stuff.class, name);
    }

    private Stuff(/*synthetic*/ String $enum$name, /*synthetic*/ int $enum$ordinal) {
        super($enum$name, $enum$ordinal);
    }
}

您可以通过创建一个临时目录并运行以下命令来查看javac如何“翻译”您的类:

javac -d <output directory> -XD-printflat filename.java

1
System.arraycopy不会更快吗?
甘道夫

+1; 如果您要解释的意思,请多加赞扬synthetic
wchargin

3
为什么要使用数组values()方法?为什么不直接将其退回?clone()$VALUES
RestInPeace 2015年

4
@RestInPeace,因为数组是可变的。有关示例,请参见stackoverflow.com/a/16125639/488861
Rytek

2
我对此一无所知-XD-printflat。了解此类事情的内部运作非常有用。遗憾的是没有对此进行更好的记录。感谢您让我意识到这一点!
MvG '16

2

如果将其分配给局部变量,则唯一可以修改的就是将另一个枚举分配给该变量。这不会更改枚举本身,因为您只更改变量引用的对象。

似乎枚举实际上是单例的,因此每个枚举中只有一个元素可以存在于整个程序中,这使==运算符对枚举合法。

因此,不存在性能问题,并且您不能意外更改枚举定义中的某些内容。


2
我认为OP意味着修改values()返回的数组。如果这是内部保留的同一数组对象(而不是副本),则对其进行修改(例如,将一个元素分配给另一个元素,将null分配给元素等)不仅会使枚举类混乱,还会使它弄乱,但对于将来对values()的任何调用
-newacct

1
是的,你是对的。如果没有克隆,则可以从数组中删除一个Enum,以后从值进行的调用将丢失该值。
Janusz,2009年

但是,这只是一个浅表副本,因为枚举实例是唯一的。
Jose Tepedino

1

它的代码是本地的吗?还是JVM /编译器对它进行了特殊处理,仅当无法证明不会对其进行修改时才从values()返回一个新实例。

1)否。或者至少在当前的实现中没有。有关证据,请参见@lucasmo的答案。

2)AFAIK,不。

假设它可以做到这一点。但是,证明阵列永远不会在本地修改对于JIT而言将是复杂且相对昂贵的。如果数组从调用的方法“转义”values(),它将变得更加复杂和昂贵。

当对所有Java代码求平均值时,这种(假设的)优化可能不会奏效。

另一个问题是这种(假设的)优化可能会打开安全漏洞。


有趣的是,JLS似乎没有指定values()成员返回数组副本。常识1表示必须做...但是实际上并没有指定。

1-如果values()返回一个共享(可变)enum值数组,这将是一个巨大的安全漏洞。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.