2

谁能解释为什么 toString() 和 name() 引用相同的字符串?当我使用 == 将它们与字符串文字进行比较时,它们都通过了!枚举名称如何与来自 JVM 的字符串池一起使用?

static enum User
{
   BASIC, PREMIUM;
}

System.out.println("BASIC" == User.BASIC.toString()); // true
System.out.println("BASIC" == User.BASIC.name());     // true
4

4 回答 4

5

好吧,Enum.name()返回Enum.toString()相同的私有字段,所以引用总是相同的。两个调用都返回name并且name == name总是正确的。

但是,为了更好地回答您的问题,JVM 的内部字符串池仅存储一份不同字符串的副本。您只请求一个不同的字符串,"BASIC"并且由于Strings 是不可变的,因此它只存储一次,.toString()并且.name()即使这些调用返回不同的字段也可能返回相同的引用。

编辑:此外,字符串文字(源代码中引号中的字符串)都是在编译时收集的,并且所有重复项都映射到同一个引用。因此,例如,如果您在整个源代码中都有使用文字的地方"Hello I am a string literal",那么该确切的字符串只会存储一次,并且由于字符串是不可变的并且永远不会改变,因此在您的使用该文字的每个地方源代码现在使用对存储在 JVM 字符串池中的单个位置的引用。这是因为,如果可能的话,显然最好不要复制一堆相同的东西。这是一个巨大的过度简化,但你明白了。

于 2016-03-31T20:44:00.837 回答
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文件,它在运行时唯一的区别在于它扩展EnumACC_ENUM设置了标志。其他一切都只是普通的字节码。

要设置枚举常量(包括它们的名称),编译器理论上可以使用复杂的反射从值名称中派生名称,但内联名称和字符串常量一样简单且有效。静态初始化程序遍历名称,调用私有构造函数来实例化值实例并将它们分配给私有$VALUES数组。

由于这些字符串位于常量池中,因此通常的重复数据删除逻辑适用。toString()返回相同的对象,因为它的默认实现只是简单地返回name

于 2016-03-31T20:41:42.213 回答
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()

于 2016-03-31T20:37:13.193 回答
0

JVM 实现正在重用相同的 String 常量,因为它们同时加载到同一个类中。这是您正在使用的特定 JVM 实现(可能是大多数现有实现)所做的优化。如果你这样做,你会得到错误的。

String s = (new StringBuilder("BAS")).append("IC").toString();
System.out.println(s == User.BASIC.toString());

这是因为 String 引用s是在运行时创建的。如果它们是从不同的类加载的,您也可能会得到错误。

于 2016-03-31T20:52:48.983 回答