【问题标题】:== with Enum name() and toString() [duplicate]== 带有 Enum name() 和 toString() [重复]
【发布时间】:2016-03-31 20:25:26
【问题描述】:

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

static enum User
{
   BASIC, PREMIUM;
}

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

【问题讨论】:

  • 您正在测试的所有字符串在编译时都是已知的,因此它们都将被编译为相同的引用是有道理的。
  • @khelwood 您能否详细解释一下“已知字符串”在编译期间发生的情况。当您说“已知”时,您的意思是字符串常量?没有什么像 str1 + str2 ?
  • an enum 是一种类,它的 name() 方法返回一个字符串字面量。

标签: java string enums jvm string-pool


【解决方案1】:

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

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

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

【讨论】:

  • 注意:只有同一类中的字符串常量由javac合并。类之间的字符串常量在运行时被合并。 +1
  • @Peter Lawrey:如果javac 没有在编译时进行整合,它甚至可以工作,然后可能会在运行时消耗更多的 CPU 时间。
【解决方案2】:

编译您的枚举类并使用 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。

【讨论】:

    【解决方案3】:

    因为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()

    【讨论】:

    • 我认为我的问题更像是为什么他们用字符串文字而不是 == 与 equals() 来指向相同的引用
    • 我知道这更像是一个附加评论......在我的项目中, .toString() 方法总是被覆盖......所以这个名字只是来自jave的默认返回值
    【解决方案4】:

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

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

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

    【讨论】:

    • "BAS" + "IC" == User.BASIC.toString() 怎么样?这仍然是真的
    • 如果你这样做,编译器很可能(但不一定)生成与上面相同的代码,但它可能只是优化它并创建一个“BASIC”字符串常量。我不太了解语言规范,无法告诉您是否允许这样优化它,但他的回答中使用的javap 反汇编@chrylis 向我展示了OpenJDK 7 将其编译为使用StringBuilder, append() 和 toString() 就像我展示的那样。
    • @coladict:规范要求 "BAS"+"IC" 被视为编译时常量,因此在运行时将评估为与"BASIC" 相同的对象。相反,String x="BAS", s=x+"IC" 将产生一个不同的实例,通常通过StringBuilder。但添加 final 将再次改变游戏规则。
    猜你喜欢
    • 1970-01-01
    • 2012-06-06
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 2016-05-18
    • 2012-12-09
    • 2018-08-31
    • 1970-01-01
    相关资源
    最近更新 更多