== 使用 Enum name() 和 toString()
== with Enum name() and toString()
谁能解释为什么 toString() 和 name() 引用同一个字符串?当我使用 == 将它们与字符串文字进行比较时,它们都通过了!枚举名称如何与 JVM 中的字符串池一起使用?
static enum User
{
BASIC, PREMIUM;
}
System.out.println("BASIC" == User.BASIC.toString()); // true
System.out.println("BASIC" == User.BASIC.name()); // true
因为 java 中的枚举 class 看起来像这样:
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()
编译你的枚举 class 并用 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()
return 是同一个对象,因为它的默认实现只是 return name
.
嗯,Enum.name()
和 Enum.toString()
return 是同一个私有字段,所以引用总是相同的。 return name
和 name == name
这两个调用总是为真。
然而,为了更好地回答您的问题,JVM 的内部字符串池只存储不同字符串的一个副本。您只请求一个不同的字符串 "BASIC"
,并且由于 String
是不可变的,它只存储一次,因此 .toString()
和 .name()
可能 return相同的引用,即使这些调用 returning 不同的字段。
编辑:
此外,字符串文字(源代码中引号中的字符串)都在编译时收集起来,任何重复项都映射到相同的引用。因此,例如,如果您在整个源代码中都有使用文字 "Hello I am a string literal"
的地方,那么该确切的字符串只会存储一次,并且由于字符串是不可变的并且永远不会改变,因此每个地方在您的源代码中使用该文字现在使用对它存储在 JVM 字符串池中的单个位置的引用。这是因为,如果可能的话,最好不要复制同一件东西。这是一个巨大的过度简化,但你明白了。
JVM 实现正在重用相同的字符串常量,因为它们同时加载到相同的 class 中。这是您正在使用的特定 JVM 实现(可能还有大多数现有实现)所做的优化。如果你这样做,你会得到 false。
String s = (new StringBuilder("BAS")).append("IC").toString();
System.out.println(s == User.BASIC.toString());
这是因为字符串引用 s
是在运行时创建的。
如果它们是从不同的 classes.
加载的,您也可能会得到 false
谁能解释为什么 toString() 和 name() 引用同一个字符串?当我使用 == 将它们与字符串文字进行比较时,它们都通过了!枚举名称如何与 JVM 中的字符串池一起使用?
static enum User
{
BASIC, PREMIUM;
}
System.out.println("BASIC" == User.BASIC.toString()); // true
System.out.println("BASIC" == User.BASIC.name()); // true
因为 java 中的枚举 class 看起来像这样:
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()
编译你的枚举 class 并用 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()
return 是同一个对象,因为它的默认实现只是 return name
.
嗯,Enum.name()
和 Enum.toString()
return 是同一个私有字段,所以引用总是相同的。 return name
和 name == name
这两个调用总是为真。
然而,为了更好地回答您的问题,JVM 的内部字符串池只存储不同字符串的一个副本。您只请求一个不同的字符串 "BASIC"
,并且由于 String
是不可变的,它只存储一次,因此 .toString()
和 .name()
可能 return相同的引用,即使这些调用 returning 不同的字段。
编辑:
此外,字符串文字(源代码中引号中的字符串)都在编译时收集起来,任何重复项都映射到相同的引用。因此,例如,如果您在整个源代码中都有使用文字 "Hello I am a string literal"
的地方,那么该确切的字符串只会存储一次,并且由于字符串是不可变的并且永远不会改变,因此每个地方在您的源代码中使用该文字现在使用对它存储在 JVM 字符串池中的单个位置的引用。这是因为,如果可能的话,最好不要复制同一件东西。这是一个巨大的过度简化,但你明白了。
JVM 实现正在重用相同的字符串常量,因为它们同时加载到相同的 class 中。这是您正在使用的特定 JVM 实现(可能还有大多数现有实现)所做的优化。如果你这样做,你会得到 false。
String s = (new StringBuilder("BAS")).append("IC").toString();
System.out.println(s == User.BASIC.toString());
这是因为字符串引用 s
是在运行时创建的。
如果它们是从不同的 classes.