我需要将序数files[i]
值转换为Java中的枚举值。这很简单:
int
MyEnumType value = MyEnumType.values()[ordinal];
方法是隐含的,我找不到它的源代码,因此问题。
values()
是否分配了新数组?如果确实如此,我应该在第一次调用时缓存数组吗?假设转换将经常被调用。
答案 0 :(得分:25)
是的,MyEnumType.values()
每次都会创建一个用枚举元素填充的新数组。您只需使用==
运算符即可对其进行测试。
MyEnumType[] arr1 = MyEnumType.values();
MyEnumType[] arr2 = MyEnumType.values();
System.out.println(arr1==arr2); //false
Java没有让我们创建不可修改数组的机制。因此,如果values()
始终返回相同的数组实例,我们就会冒险让某人为每个人更改其内容
因此,直到将不可修改的数组机制引入Java,以避免数组可变性问题values()
方法必须创建并返回原始数组的副本。
如果你想避免重新创建这个数组,你可以简单地存储它并稍后重用values()
的结果。有几种方法可以做到,比如。
您可以创建私有数组,并且只允许通过像
这样的getter方法访问其内容private static final MyEnumType[] VALUES = values();// to avoid recreating array
MyEnumType getByOrdinal(int){
return VALUES[int];
}
您还可以将values()
的结果存储在List
等不可修改的集合中,以确保其内容不会被更改(现在这样的列表可以公开)。
public static final List<X> VALUES = Collections.unmodifiableList(Arrays.asList(values()));
答案 1 :(得分:13)
理论上,values()
方法每次都必须返回一个新数组,因为Java没有不可变数组。如果它总是返回相同的数组,则无法阻止调用者通过修改数组来互相混淆。
我无法找到它的源代码
values()
方法没有普通的源代码,而是由编译器生成的。对于javac,生成values()
方法的代码位于com.sun.tools.javac.comp.Lower.visitEnumDef。对于ECJ(Eclipse&#39;编译器),代码位于 org.eclipse.jdt.internal.compiler.codegen.CodeStream.generateSyntheticBodyForEnumValues。
查找values()
方法实现的更简单方法是反汇编编译的枚举。首先创建一些愚蠢的枚举:
enum MyEnumType {
A, B, C;
public static void main(String[] args) {
System.out.println(values()[0]);
}
}
然后编译它,并使用JDK中包含的javap工具对其进行反汇编:
javac MyEnumType.java && javap -c -p MyEnumType
输出中可见的是枚举的所有编译器生成的隐式成员,包括(1)每个枚举常量的static final
字段,(2)包含所有常量的隐藏$VALUES
数组,(3)一个静态初始化块,它实例化每个常量并将每个常量分配给它的命名字段和数组,以及(4)values()
方法通过调用.clone()
$VALUES
来实现数组并返回结果:
final class MyEnumType extends java.lang.Enum<MyEnumType> {
public static final MyEnumType A;
public static final MyEnumType B;
public static final MyEnumType C;
private static final MyEnumType[] $VALUES;
public static MyEnumType[] values();
Code:
0: getstatic #1 // Field $VALUES:[LMyEnumType;
3: invokevirtual #2 // Method "[LMyEnumType;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LMyEnumType;"
9: areturn
public static MyEnumType valueOf(java.lang.String);
Code:
0: ldc #4 // class MyEnumType
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class MyEnumType
9: areturn
private MyEnumType(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #8 // Method values:()[LMyEnumType;
6: iconst_0
7: aaload
8: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
static {};
Code:
0: new #4 // class MyEnumType
3: dup
4: ldc #10 // String A
6: iconst_0
7: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #12 // Field A:LMyEnumType;
13: new #4 // class MyEnumType
16: dup
17: ldc #13 // String B
19: iconst_1
20: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #14 // Field B:LMyEnumType;
26: new #4 // class MyEnumType
29: dup
30: ldc #15 // String C
32: iconst_2
33: invokespecial #11 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #16 // Field C:LMyEnumType;
39: iconst_3
40: anewarray #4 // class MyEnumType
43: dup
44: iconst_0
45: getstatic #12 // Field A:LMyEnumType;
48: aastore
49: dup
50: iconst_1
51: getstatic #14 // Field B:LMyEnumType;
54: aastore
55: dup
56: iconst_2
57: getstatic #16 // Field C:LMyEnumType;
60: aastore
61: putstatic #1 // Field $VALUES:[LMyEnumType;
64: return
}
然而,values()
方法必须返回一个新数组这一事实并不意味着编译器必须使用该方法。编译器可能会检测到MyEnumType.values()[ordinal]
的使用,并且看到数组未被修改,它可以绕过该方法并使用底层的$VALUES
数组。上面对main
方法的反汇编表明,javac 不进行了这样的优化。
我也测试了ECJ。反汇编显示ECJ还初始化一个隐藏数组来存储常量(尽管Java langspec并不需要),但有趣的是它的values()
方法更喜欢创建一个空白数组然后用{{1}填充它而不是调用System.arraycopy
。无论哪种方式,.clone()
每次都会返回一个新数组。像javac一样,它并没有尝试优化序数查找:
values()
然而,JVM仍然可能有一个优化,可以检测到数组被复制然后被丢弃的事实,并避免它。为了测试这一点,我运行了以下一对基准程序,它们在循环中测试顺序查找,每次调用final class MyEnumType extends java.lang.Enum<MyEnumType> {
public static final MyEnumType A;
public static final MyEnumType B;
public static final MyEnumType C;
private static final MyEnumType[] ENUM$VALUES;
static {};
Code:
0: new #1 // class MyEnumType
3: dup
4: ldc #14 // String A
6: iconst_0
7: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #19 // Field A:LMyEnumType;
13: new #1 // class MyEnumType
16: dup
17: ldc #21 // String B
19: iconst_1
20: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #22 // Field B:LMyEnumType;
26: new #1 // class MyEnumType
29: dup
30: ldc #24 // String C
32: iconst_2
33: invokespecial #15 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #25 // Field C:LMyEnumType;
39: iconst_3
40: anewarray #1 // class MyEnumType
43: dup
44: iconst_0
45: getstatic #19 // Field A:LMyEnumType;
48: aastore
49: dup
50: iconst_1
51: getstatic #22 // Field B:LMyEnumType;
54: aastore
55: dup
56: iconst_2
57: getstatic #25 // Field C:LMyEnumType;
60: aastore
61: putstatic #27 // Field ENUM$VALUES:[LMyEnumType;
64: return
private MyEnumType(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #31 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
6: return
public static void main(java.lang.String[]);
Code:
0: getstatic #35 // Field java/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #41 // Method values:()[LMyEnumType;
6: iconst_0
7: aaload
8: invokevirtual #45 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
public static MyEnumType[] values();
Code:
0: getstatic #27 // Field ENUM$VALUES:[LMyEnumType;
3: dup
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
8: dup
9: istore_1
10: anewarray #1 // class MyEnumType
13: dup
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic #53 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
20: aload_2
21: areturn
public static MyEnumType valueOf(java.lang.String);
Code:
0: ldc #1 // class MyEnumType
2: aload_0
3: invokestatic #59 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #1 // class MyEnumType
9: areturn
}
,另一个使用数组的私有副本。序数查找的结果被分配给values()
字段,以防止它被优化掉:
volatile
我在服务器VM上的Java 8u60上运行了这个。使用enum MyEnumType1 {
A, B, C;
public static void main(String[] args) {
long t = System.nanoTime();
for (int n = 0; n < 100_000_000; n++) {
for (int i = 0; i < 3; i++) {
dummy = values()[i];
}
}
System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t) / 1e9);
}
public static volatile Object dummy;
}
enum MyEnumType2 {
A, B, C;
public static void main(String[] args) {
long t = System.nanoTime();
for (int n = 0; n < 100_000_000; n++) {
for (int i = 0; i < 3; i++) {
dummy = values[i];
}
}
System.out.printf("Done in %.2f seconds.\n", (System.nanoTime() - t) / 1e9);
}
public static volatile Object dummy;
private static final MyEnumType2[] values = values();
}
方法的每个测试大约需要10秒,而使用私有阵列的每个测试大约需要2秒。使用values()
JVM参数显示,在使用-verbose:gc
方法时存在重要的垃圾收集活动,而在使用私有数组时则没有。在客户端虚拟机上运行相同的测试,私有阵列仍然很快,但values()
方法变得更慢,花了一分多钟才完成。调用values()
也需要更长的时间来定义更多的枚举常量。所有这些都表明values()
方法确实每次都会分配一个新数组,避免它可能是有利的。
请注意,java.util.EnumSet
和java.util.EnumMap
都需要使用枚举常量数组。为了提高性能,他们调用JRE专有代码来缓存存储在values()
中的values()
in a shared array的结果。您可以通过调用java.lang.Class
来自己访问该共享阵列,但依赖它是不安全的,因为这些API不是任何规范的一部分,可以在任何Java更新中更改或删除。
结论:
sun.misc.SharedSecrets.getJavaLangAccess().getEnumConstantsShared(MyEnumType.class)
方法必须表现得好像总是分配一个新数组,以防来电者修改它。