55
class Test {
    public static void main(String...args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1.intern());
        String s2 = "Goodmorning";
        if (s1 == s2) {
            System.out.println("both are equal");
        }
    }
}

This code produces different outputs in Java 6 and Java 7. In Java 6 the s1==s2 condition returns false and in Java 7 the s1==s2 returns true. Why?

Why does this program produces different output in Java 6 and Java 7?

Mohammad Faisal
  • 5,783
  • 15
  • 70
  • 117
  • 3
    With which Java implementation does merely _calling_ `intern()` on the value of a local String variable (and not assigning the return value back to the local variable) magically make the variable's value compare equal to a literal??? – hmakholm left over Monica Aug 15 '11 at 13:21
  • Only after I added `s1 = s1.intern()` does the second snippet print out "both are equal". Are you sure the code is the same as what you have? – zw324 Aug 15 '11 at 13:31
  • 1
    @Mohammad Faisal Which JVM are you running? – nfechner Aug 15 '11 at 13:31
  • 1
    @Mohammad - is that the CORRECT code? not missing a `s1 = s1.intern()` or is it `if (s1.intern() == s2)`? Just calling `intern()` should not change `s1`. – user85421 Aug 15 '11 at 13:39
  • no way! i had checked it all and i'm not missing anything. the code is exact it is. i'm using jdk7 – Mohammad Faisal Aug 15 '11 at 14:02
  • 6
    This question is asking about reference equality, not == vs. .equals(). – Bill the Lizard Aug 15 '11 at 14:45
  • still **not** getting equal Strings in the second code: http://ideone.com/7qcYo (and using @reply in comments would help...) – user85421 Aug 16 '11 at 17:21
  • @Carlos Heuberger: I'm using jdk7 and I'm getting `both are equal` in the second program. But when I run the same code using jdk6 I'm not getting `both are equal`. So there must be some research efforts. – Mohammad Faisal Aug 17 '11 at 03:07
  • 1
    @Faisal, isn't the Carlos's answer that is explaining (or suggesting) the behavioural change between java 6 and java 7? Nathan's answer providing great info though. – Reddy Aug 23 '11 at 15:55
  • @Reddy, though Carlos's answer was explaining or suggesting but the answer provided by Nathan is not yet clear because the second code in `jdk6` has `if(s1==s2)` as `false` but the same code in `jdk7` has `if(s1==s2)` as `true` why? – Mohammad Faisal Aug 23 '11 at 23:49
  • 1
    @Faisal, My point is even though Nathan explains the intern and string pool he didn't explain why there is different behaviour in java6 and 7. But Carlos's answer atleast giving pointer to a change made by Oracle in JDK which might be causing this. – Reddy Aug 24 '11 at 10:10
  • why i'm not getting any reputation for vote ups of this question? Can anybody tell me? Before starting bounty it got 5 vote up but no reputation is awarded. Now after starting bounty it got 2 vote up but no increment in reputation. Why? – Mohammad Faisal Aug 30 '11 at 04:16
  • 1
    @Mohammad This post is community owned as of Aug 15 at 14:27. Votes do not generate reputation, and it can be edited by users with 100 rep – Andrey Aug 30 '11 at 07:25
  • Just did some testing on JDK 1.6.32, somehow == is having same behaviour as equals() now. – Rudy Jul 20 '12 at 10:36
  • It's amusing to me how many people posting in this question jump to "`==` is the wrong way to test for String equality!" without even taking the time to understand the question. – dimo414 Feb 28 '13 at 06:37

9 Answers9

27

It seems that JDK7 process intern in a different way as before.
I tested it with build 1.7.0-b147 and got "both are equal", but when executing it (same bytecode) with 1,6.0_24 I do not get the message.
It also depends where the String b2 =... line is located in the source code. The following code also does not output the message:

class Test {
   public static void main(String... args) {
      String s1 = "Good";
      s1 = s1 + "morning";

      String s2 = "Goodmorning";
      System.out.println(s1.intern());  //just changed here s1.intern() and the if condition runs true   

      if(s1 == s2) {
         System.out.println("both are equal");
      } //now it works.
   }
}

it seems like intern after not finding the String in its pool of strings, inserts the actual instance s1 into the pool. The JVM is using that pool when s2 is created, so it gets the same reference as s1 back. On the other side, if s2 is created first, that reference is stored into the pool.
This can be a result of moving the interned Strings out from the permanent generation of the Java heap.

Found here: Important RFEs Addressed in JDK 7

In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.

Not sure if that is a bug and from which version... The JLS 3.10.5 states

The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.

so the question is how pre-existing is interpreted, compile-time or execute-time: is "Goodmorning" pre-existing or not?
I prefer the way it WAS implemented before 7...

Kai
  • 38,985
  • 14
  • 88
  • 103
user85421
  • 28,957
  • 10
  • 64
  • 87
  • Should that be considered as bug? – Reddy Aug 23 '11 at 15:56
  • @Reddy - not sure, seams that it is not specified exactly how it should be... The documentation of intern states that "this String" is stored and returned if it is not already in the pool, but I found no definition when literals should be saved into the pool. – user85421 Aug 23 '11 at 16:49
25

Let's omit unnecessary details from the example:

class Test {
    public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

Let's consider String#intern as a black box. Based on a few test cases run, I would conclude that implementation is as following:

Java 6:
if the pool contains object equals to this, then return reference to that object, else create new string (equal to this), put to the pool, and return reference to that created instance.

Java 7:
if the pool contains object equals to this, then return reference to that object, else put this to the pool, and return this.

Neither Java 6 nor Java 7 breaks the contract of the method.

It seems that new intern method behavior was a result of the fix of this bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962931.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
Andrey
  • 6,526
  • 3
  • 39
  • 58
  • 1
    `In jdk7 interning approach was modified, and now the method has a possibility put to the pool and return the passed instance directly.` i'm not getting `return the passed instance directly.` Is this is specified by `Sun` or `Oracle` anywhere? – Mohammad Faisal Aug 29 '11 at 05:42
  • @Mohammad I have reformulated my answer. – Andrey Aug 29 '11 at 10:40
  • this should be the accepted answer. concise and precise. Explains perfectly why the same code works differently in both cases – Clint Eastwood Feb 10 '14 at 14:21
9

== compares the references. The intern method makes sure strings with the same value have the same reference.

The javadoc for the String.intern method explains:

public String intern()

Returns a canonical representation for the string object.

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the Java Language Specification

Returns: a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

So without interning the compiler looks at the constants in the java code and builds its constant pool from that. There is a different pool maintained by the String class, and interning checks the string passed in against the pool and makes sure the reference is unique (so that == will work).

Community
  • 1
  • 1
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • yes i know `==` compares the references and i've to same. But what for the first program? isn't both `s1` and `s2` have the same reference? or what about the second program, when i write `System.out.println(s1.intern());`; now both have same reference why? – Mohammad Faisal Aug 15 '11 at 13:21
  • No, they're not the same reference. You have two different variables pointing at two different strings that just happen to contain the same data. If they always were the same reference there wouldn't be a need for an intern method. – Nathan Hughes Aug 15 '11 at 13:39
  • sorry! but if i do it like: `class Test{ public static void main(String... args){ String s1="hi"; String s2="hi"; if(s1==s2){ System.out.println("equal");//and it works } } }` now how can these Strings (s1 and s2) be equal? – Mohammad Faisal Aug 15 '11 at 14:39
  • 1
    in your question your code was working to trick the jvm, here it's easy to figure out, so the jvm goes ahead and uses the same reference. It's looking out for easy optimizations. – Nathan Hughes Aug 15 '11 at 14:42
  • 1
    i'm not getting. The thing i know is when we say `String s1="Good";` there is an object of String type created in the Constant Pool. And when i say `s1=s1+"morning";` there is another String object created as `Goodmorning` and reference of that is assigned to `s1`. Now, when i say `String s2="Goodmorning";` then it checks whether `Goodmorning` is in the Constant Pool? and if it is found than the reference of previous `Goodmorning` is assigned to the `s2` that means `s1==s2` but in first program it doesn't work and in the second it works. How? – Mohammad Faisal Aug 15 '11 at 14:53
  • 3
    @Mohammad: it checks what's in the constant pool at the time the class is compiled. so it can't take into account string concatenations, etc. intern reassigns the references at runtime. – Nathan Hughes Aug 15 '11 at 14:56
  • @NathanHughes let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/2509/discussion-between-mohammad-faisal-and-nathan-hughes) – Mohammad Faisal Aug 16 '11 at 05:17
  • @mohammad: sorry i must've overlooked this message. email me at nathan.d.hughes@gmail.com if you have more questions. – Nathan Hughes Aug 18 '11 at 12:56
7

In jdk6: String s1="Good"; creates a String object "Good" in constant pool.

s1=s1+"morning"; creates another String object "morning" in constant pool but this time actually JVM do: s1=new StringBuffer().append(s1).append("morning").toString();.

Now as the new operator creates an object in heap therefore the reference in s1 is of heap not constant pool and the String s2="Goodmorning"; creates a String object "Goodmorning" in constant pool whose reference is stored in s2.

Therefore, if(s1==s2) condition is false.

But what happens in jdk7?

Reddy
  • 8,737
  • 11
  • 55
  • 73
Mohammad Faisal
  • 5,783
  • 15
  • 70
  • 117
  • probably that is related to the changes Carlos Heuberger mentioned in http://stackoverflow.com/questions/7065337/two-strings-which-are-equal-but-on-comparison-results-unequal/7090813#7090813 – Reddy Aug 18 '11 at 13:11
6

FIRST CASE:

In the first code snipped you are actually adding three Strings in the Pool of Strings. 1. s1 = "Good"
2. s1 = "Goodmorning" (after concatenating) 3. s2 = "Goodmorining"

While doing if(s1==s2), the objects are same but reference as different hence it is false.

SECOND CASE:

In this case you are using s1.intern(), which implies that if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

  1. s1 = "Good"
  2. s1 = "Goodmorning" (after concatenating)
  3. For String s2="Goodmorning", new String is not added to the pool and you get reference of existing one for s2. Hence if(s1==s2) returns true.
Sachin Karjatkar
  • 313
  • 3
  • 13
  • 2
    point 3 is valid (only?) for JDK7. With JDK6 `s1 == s2` returns **false** since `intern()` apparently stores a different instance/reference (same chars) in the pool. – user85421 Aug 18 '11 at 08:50
5

You need to use s1.equals(s2). Using == with String objects compares the object references themselves.

Edit: When I run your second code snippet, I do not get "both are equal" printed out.

Edit2: Clarified that references are compared when you use '=='.

Datajam
  • 4,141
  • 2
  • 23
  • 25
  • 1
    but i'm getting it. "both are equal" in the second program – Mohammad Faisal Aug 15 '11 at 13:23
  • You must be mistaken. Are you sure you haven't got `s1==s1` in the `if` statement by mistake? Or perhaps `s1=s2` before the `if`? – Datajam Aug 15 '11 at 13:30
  • sorry! but if i do it like: `class Test{ public static void main(String... args){ String s1="hi"; String s2="hi"; if(s1==s2){ System.out.println("equal");//and it works } } }` – Mohammad Faisal Aug 15 '11 at 14:07
  • The best practice for comparing strings is of course to use `.equals()`, that isn't the point of the question. As String objects are immutable, different references to the same set of characters may or may not point to the same instance. The specifics of when this happens are a JVM optimization, and therefore not defined. The question points out that the implementation changes between Java 6 and Java 7, and is wondering why. – dimo414 Feb 28 '13 at 06:35
4

there are mainly 4 ways to compare string:

  1. "== operator": it just compares the reference variable of the string object. So it might give you unexpected results depending upon how you have created the string i.e. using String class's constructor or simply by using double quote as both get memory differently(in heap and pool respectively).
  2. "equals(Object) method": this is method of object class and is OVERLOADED by string class. It compares whole string and IS CASE SENSITIVE.
  3. "equalsIgnoreCase(String) method": this is method of string class and compares whole string and IS NOT CASE SENSITIVE.
  4. "compares(String) method": compare both strings character by character and return their difference if the returned value is 0, this means strings are equal.
3

Whenever you are comparing between two String, don't use == and use eqauls() becaue you are comparing objects not references:

string1.equals(string2);
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • 4
    i know what i'm doing. Check the answer by Nathan Hughes – Mohammad Faisal Aug 15 '11 at 13:28
  • @Mohammad - sure? in neither of your codes s1 is the same reference as s2, assuming Java SE from Sun/Oracle: s1 is the result of concatenating 2 Strings - a new String - s2 is from the constant pool. – user85421 Aug 15 '11 at 13:45
2

The result code dependents runtime:

class Test {
     public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

If you write like this:

class Test {
     public static void main(String... args) {
        String s = "GoodMorning";
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints false for both jdk7 and jdk6.
    }
}

the reason is ' ldc #N ' (Load string from constant pool) and String.intern() both will use StringTable in hotspot JVM. For detail I wrote a pool english article: http://aprilsoft.cn/blog/post/307.html

  • In your second code snippet, shouldn't it be `s == s1.intern()` instead of `s1 == s1.intern()` ? – t0r0X Nov 15 '12 at 17:18