任何人都可以解释为什么toString()和name()引用相同的String?当我使用==将它们与字符串文字进行比较时,它们都会通过!枚举名如何与JVM中的字符串池一起使用?
static enum User
{
BASIC, PREMIUM;
}
System.out.println("BASIC" == User.BASIC.toString()); // true
System.out.println("BASIC" == User.BASIC.name()); // true
答案 0 :(得分:5)
好吧,Enum.name()
和Enum.toString()
返回相同的私有字段,因此引用始终是相同的。两个调用返回name
,name == name
始终为真。
但是,为了更好地回答您的问题,JVM的内部字符串池仅存储一个不同字符串的副本。您只是请求一个不同的字符串"BASIC"
,并且由于String
是不可变的,因此它只存储一次,因此.toString()
和.name()
可能会返回相同的引用,即使那些电话回归了不同的领域。
编辑:
此外,字符串文字(源代码中的引号中的字符串)都在编译时收集,任何重复项都映射到相同的引用。因此,例如,如果您在源代码的所有位置使用文字"Hello I am a string literal"
,那么这个确切的字符串只存储一次,因为字符串是不可变的并且永远不会改变,所以每个地方都是在源代码中使用该文字现在使用对它存储在JVM字符串池中的单个位置的引用。这是因为,如果可能的话,最好不要制作同一件事的一堆副本。这是一个巨大的过度简化,但你明白了。
答案 1 :(得分:3)
编译你的枚举类并用javap -verbose
反汇编得到这个(部分)输出:
final class User extends java.lang.Enum<User>
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#7 = String #13 // BASIC
#9 = Fieldref #4.#38 // User.BASIC:LUser;
#10 = String #15 // PREMIUM
#11 = Fieldref #4.#39 // User.PREMIUM:LUser;
#13 = Utf8 BASIC
#15 = Utf8 PREMIUM
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class User
3: dup
4: ldc #7 // String BASIC
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field BASIC:LUser;
13: new #4 // class User
16: dup
17: ldc #10 // String PREMIUM
19: iconst_1
20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #11 // Field PREMIUM:LUser;
26: iconst_2
27: anewarray #4 // class User
30: dup
31: iconst_0
32: getstatic #9 // Field BASIC:LUser;
35: aastore
36: dup
37: iconst_1
38: getstatic #11 // Field PREMIUM:LUser;
41: aastore
42: putstatic #1 // Field $VALUES:[LUser;
45: return
LineNumberTable:
line 1: 0
编译枚举时,它只是一个普通的Java .class
文件,它在运行时唯一的区别特征是它扩展Enum
并具有ACC_ENUM
标志组;其他一切都只是普通的字节码。
要设置枚举常量,包括它们的名称,编译器理论上可以使用复杂的反射从值名称中派生名称,但是它更简单,并且将名称内联为字符串常量同样有效。静态初始化程序遍历名称,调用私有构造函数来实例化值实例并将它们分配给私有$VALUES
数组。
由于这些字符串位于常量池中,因此通常的重复数据删除逻辑适用。 toString()
返回相同的对象,因为它的默认实现只是返回name
。
答案 2 :(得分:1)
因为java中的枚举类看起来像这样:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
一般情况下,您应该将枚举与==进行比较,但如果您想使用名称请使用.equals()
答案 3 :(得分:0)
JVM实现正在重用相同的String常量,因为它们同时被加载到同一个类中。这是您正在使用的特定JVM实现(可能是大多数现有实现)所做的优化。如果你这样做,你就会变得虚假。
String s = (new StringBuilder("BAS")).append("IC").toString();
System.out.println(s == User.BASIC.toString());
这是因为字符串引用s
是在运行时创建的。
如果他们从不同的班级加载,你也可能会错误。