4

The following codes has different results under JDK8 1.8.0_171

@Test
public void test2() {
    String s1 = new String("a") + new String("a");
    s1.intern();
    String s2 = "aa";
    System.out.println(s1 == s2); //true
}
@Test
public void test3() {
    String s1 = new String("1") + new String("1");
    s1.intern();
    String s2 = "11";
    System.out.println(s1 == s2); //false
}

The only difference is the value: "a" instead of "1", the result I get is different. Why is this?

Stultuske
  • 9,296
  • 1
  • 25
  • 37
anan.kun
  • 59
  • 4
  • 1
    I'd not rely on `==` for string comparisons at all, i.e. whether they have been interned or not shouldn't matter. Thus the question might be moot from a practical point of view. – Thomas Apr 13 '21 at 07:17

3 Answers3

8

s1.intern() only adds the String referenced by s1 to the String pool if the pool doesn't already contain a String equal to it.

String literals such as "aa" and "11" are always interned, so they will always be == the instance returned by s1.intern().

Therefore, whether or not s1 == s2 returns true depends on whether or not the String pool contained a String equals to s1 before s1.intern() was called.

  • If it did, s1.intern() which is == s2 is not == s1, so System.out.println(s1 == s2) will print false.
  • If it did not, s1.intern () == s1 == s2, so System.out.println(s1 == s2) will print true.

This can change between Java versions, since JDK classes of different versions may contain a different set of String literals, which are automatically interned before your code is executed.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • To verify this: simply adding adding a `test0` that prints (or does *anything* with) the string constant `"aa"` will make `test2` print false as well (as long as `test0` is executed before `test2`). – Joachim Sauer Apr 13 '21 at 07:23
  • Clever answer. Took a few seconds to get what you were talking about. – ernest_k Apr 13 '21 at 07:25
  • 1
    Another thing that apparently changes between JVMs is whether literals are interned at class load time, class init time, or ... lazily the first time that a method is executed. – Stephen C Apr 13 '21 at 07:27
  • @ernest_k I still don't get it :( It's confusing because in `test2`, the output changes depending on whether `intern` is called or not. It seems as if `intern` "reassigns" `s1` to something else! If it were `s1 = s1.intern();`, both tests would have been true, but since the return value is ignored (`intern` merely adds the string into the string pool, but since `aa` and `11` are literals, they are already there, so `intern` does practically nothing), why does `test2` still print true? – Sweeper Apr 13 '21 at 07:31
  • 1
    @Sweeper: no, `intern` can't and doesn't change what `s1` is. But when a string value is first interned by use of that method, the actual instance you call `intern` on **becomes** a member of there intern pool. So instead of `intern` changing what `s1` is, it changes what `s2` will become when it will be initialized from the intern pool: either the value interned by `s1` or (if you don't call `intern`) it becomes a new string instance with the same value that is put into the intern pool instead. – Joachim Sauer Apr 13 '21 at 07:36
  • @StephenC: that's what confused me as well, when I first read the question. I remember string literals being loaded at (roughly) class loading/initialization time, but apparently that's no longer true in current JVMs (if one can call JDK 8 "current" ...). – Joachim Sauer Apr 13 '21 at 07:37
  • @Sweeper Yes, is the code was `s1 = s1.intern();` the result would always be true, since `s2 == s1.intern()`. If you remove the `s1.intern()` call, the result would always be false, since `s1` will never be equal to the interned instance. The difference in behavior depends on whether or not another `intern()` was called for some equal `String` (by some JDK class) prior to `s1.intern()`. – Eran Apr 13 '21 at 07:38
  • You are right. I use IDE to test them. In `test3()`, before `s1.intern()` invoke , String pool has already had String "11", so `s1.intern() ` do nothing. So s1 referenced heap instance, but s2 referenced String pool , the result of `s1 == s2` is false. – anan.kun Apr 14 '21 at 09:03
  • When I move method body to `main()` method,the result is true. – anan.kun Apr 14 '21 at 09:07
1

This is to add an illustration to Eran's great answer. The following assumes "aa" is not interned prior to the execution of the code (implicitly or not):

String s1 = new String("a") + new String("a");
s1.intern();
String s2 = "aa";
System.out.println(s1 == s2); //true

String s3 = new String("a") + new String("a"); //same text
s3.intern();
System.out.println(s3 == s2); //false

Both s1 and s3 have the same text ("aa"), so the difference in actual text doesn't make the difference. The second call to .intern() simply didn't result in s3 being put in the pool (because the pool already contained the text "aa", which was the result of s1.intern()), explaining why == returns false between s3 and s2.

In short, it means that "11" was interned prior to the execution of your test3() method.

And for an additional test, move String s2 = "aa"; to the beginning of the code (both comparisons return false, for the same reason - although this may be version or implementation-dependant)

ernest_k
  • 44,416
  • 5
  • 53
  • 99
0

Because the == operator checks if both variables reference the exact same object. For example

String s1 = new String("foo");
String s2 = s1;
System.out.println(s1 == s2); //true

If you have two separate instances and you want to compare Strings based on their values you should be using String::equals.

String s1 = new String("foo");
String s2 = new String("foo");
System.out.println(s1.equals(s2)); //true

Please check out How do I compare strings in Java?

When it comes to String:intern you'd have to call it on both instances in order to compare them, not just one of them. String foo = "foo"; still implicitly creates a new instance of a String object. There is no primitive string in Java.

String s1 = new String("foo");
String s2 = "foo";
System.out.println(s1.intern() == s2.intern());
Dropout
  • 13,653
  • 10
  • 56
  • 109
  • This is the base case information. But it doesn't explain why the two tests presented by OP would produce different output and it doesn't explain why calling `s1.intern()` *without* reasinging the result back to `s1` would change the output of the test. – Joachim Sauer Apr 13 '21 at 08:25