Java 6枚举类型的values()方法是如何实现的?

75
在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()返回新实例。

3个回答

119

基本上,编译器(javac)会在编译时将您的枚举翻译为一个包含所有值的静态数组。当您调用values()时,它会给您一个此数组的克隆副本。

给定这个简单的枚举:

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 会更快吧? - Gandalf
3
为什么 values() 方法要将数组 $VALUES 进行 clone() 操作?直接返回不可以吗? - RestInPeace
5
因为数组是可变的,所以需要使用@RestInPeace。参见https://dev59.com/4mQo5IYBdhLWcg3wV-Mx#16125639获取示例。 - Rytek
3
我之前不知道-XD-printflat这个东西,它非常有用,可以帮助我们更好地了解内部工作原理。可惜的是,这个东西的文档记录不够完善。感谢让我意识到这一点! - MvG
1
这让我明白了为什么我不能限制一个泛型方法来接受枚举类型,然后访问.values()方法。从你所说的来看,这似乎不是所有枚举类型都共有的方法,而是每个声明的枚举类型中存在的独特方法,并且与任何其他声明的枚举类型中的.values()方法没有任何共同之处。 - Ryan Lundy
显示剩余2条评论

2

如果你将它分配给一个本地变量,那么你唯一可以修改的就是将另一个枚举分配给这个变量。这不会改变枚举本身,因为你只是在改变变量引用的对象。

似乎枚举实际上是单例,所以每个枚举中只能存在一个元素,这使得 == 运算符对枚举合法。

因此,没有性能问题,你也不会意外地更改枚举定义。


2
我认为OP的意思是修改由values()返回的数组。如果这是保留在内部的同一数组对象(而不是副本),那么修改它(例如将一个元素分配给另一个元素,将null分配给一个元素等)会使其混乱,不仅对于枚举类,而且对于任何未来调用values()的调用。 - newacct
1
是的,您说得对。如果没有克隆,您可能会从数组中删除一个枚举,稍后从值调用将错过此值。 - Janusz
然而这只是一个浅拷贝,因为枚举实例是唯一的。 - Jose Tepedino

1
“它”的代码是原生的吗? JVM/编译器是否对其进行特殊处理,只有在无法证明其不会被修改时才从values()返回新实例?
1)不是。或者至少在当前的实现中不是。有证据见@lucasmo的回答。
2)据我所知,没有。
理论上讲,它可以这样做。然而,证明数组在本地永远不会被修改将变得复杂,对于JIT来说也相对昂贵。如果数组“逃离”了调用values()方法的方法,那么它会变得更加复杂和昂贵。
很可能这种(假设的)优化在所有Java代码上平均起来是得不偿失的。另一个问题是,这种(假设的)优化可能会开启安全漏洞。
有趣的是,JLS似乎没有指定values()成员返回一个数组副本。常识认为它必须这样做...但实际上并没有明确规定。
1 - 如果values()返回一个共享(可变)的enum值数组,那么这将是一个巨大的安全漏洞。

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