2

Can anyone explain why toString() and name() are referencing the same String? when I used == to compare them with a String literal they all pass! How does enum name work with String pool from JVM?

static enum User
{
   BASIC, PREMIUM;
}

System.out.println("BASIC" == User.BASIC.toString()); // true
System.out.println("BASIC" == User.BASIC.name());     // true
peter
  • 8,333
  • 17
  • 71
  • 94
  • 5
    All the strings you're testing are known at compile time, so it makes sense that they would all be compiled to the same reference. – khelwood Mar 31 '16 at 20:28
  • @khelwood can you explain more on what happen during compile time for "known strings". And when you say "known" you mean String constant ? nothing like str1 + str2 ? – peter Mar 31 '16 at 20:53
  • an `enum` is a type of class and it's `name()` method returns a String literal. – Peter Lawrey Apr 01 '16 at 08:12

4 Answers4

6

Well, Enum.name() and Enum.toString() return the same private field, so the references are always going to be the same. Both calls return name and name == name is always going to be true.

To better answer your question, however, the JVM's internal string pool stores only one copy of distinct strings. You're only requesting one distinct string, "BASIC", and since Strings are immutable it is stored only once so .toString() and .name() would likely return the same reference even if those calls were returning different fields.

EDIT: Also, string literals (strings in quotes in source code) are all gathered up at compile time and any duplicates are all mapped to the same reference. So if, for instance, you have places all over your source code where you're using the literal "Hello I am a string literal", then that exact string is only stored one time and, since strings are immutable and will never change, every place that used that literal in your source code now uses a reference to the single place where it's stored in the JVM String pool. This is because, if possible, it's obviously better to not make a bunch of copies of the same thing. That's a vast oversimplification but you get the idea.

  • Note: only string constants in the same class are consolidated by the `javac`. String constants between classes are consolidated at runtime. +1 – Peter Lawrey Apr 01 '16 at 08:11
  • 1
    @Peter Lawrey: it would even work if `javac` did not consolidate at compile-time, then potentially consuming more CPU time at runtime. – Holger Apr 02 '16 at 13:00
3

Compiling your enum class and disassembling with javap -verbose gives this (partial) output:

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

By the time that an enum is compiled, it's just an ordinary Java .class file, whose only distinguishing features at runtime are the fact that it extends Enum and has the ACC_ENUM flag set; everything else is just plain bytecode.

To set up the enum constants, including their names, the compiler could theoretically use complicated reflection to derive the names from the value names, but instead it's far simpler and just as effective to inline the names as string constants. The static initializer loops through the names, calling the private constructor to instantiate the value instances and assign them to the private $VALUES array.

As these strings are in the constant pool, the usual deduplication logic then applies. toString() returns the same object because its default implementation is simply to return name.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
1

because the enum class in java looks like this:

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;
}

in general you should compare enums with == but if you want to use the name pls use .equals()

John
  • 142
  • 1
  • 11
  • I think my question is more like why they are pointing to the same reference with a String literal rather than == versus equals() – peter Mar 31 '16 at 20:42
  • I know it was more an additional comment... in my projects the .toString() method is always overwritten ... so the name is juste a default return value from jave – John Mar 31 '16 at 20:47
0

The JVM implementation is reusing the same String constant, because they're both loaded in the same class at the same time. That is an optimization made by the specific JVM implementation you're using (and probably most of the existing ones). If you did this, you would get false.

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

This is because the String reference s is created at runtime. You would also likely get false if they're loaded from different classes.

coladict
  • 4,799
  • 1
  • 16
  • 27
  • what about "BAS" + "IC" == User.BASIC.toString() ? this is still true – peter Mar 31 '16 at 20:55
  • The compiler will most likely (but will not necessarily) produce the same code as above if you do that, but it might just optimize it away and create a "BASIC" string constant. I do not know the language specifications well enough to tell you if it's allowed to optimize it like that, but the `javap` disassembly @chrylis used in his answer has shown me that OpenJDK 7 compiles it to the use of a `StringBuilder`, `append()` and `toString()` like I've shown. – coladict Mar 31 '16 at 21:02
  • @coladict: the specification *requires* that `"BAS"+"IC"` is treated as compile-time constant and hence, will evaluate to the same object as `"BASIC"` at runtime. In contrast, `String x="BAS", s=x+"IC"` will produce a distinct instance, usually via `StringBuilder`. But prepending `final ` will change the game again. – Holger Apr 02 '16 at 13:07