== 使用 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 namename == 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