4

I understand that, given the immutability of strings, something like

String a="";
for(int i=0;i++<9;)
    a+=i;

is highly inefficient, because initially a string is instantiated and put in the string pool, then with a+=i a new string is created (0 in the first loop), referenced by a and the previous one is now eligible for garbage collection. And this happens nine times.

A better way is using StringBuilder:

StringBuilder a=new StringBuilder("");
for(int i=0;i++<9;)
    a.append(i);

But what happens behind the scenes when I instantiate the string with the new keyword?

String a=new String("");
for(int i=0;i++<9;)
    a+=i;

I know that in this case a doesn't get interned (it's not in the string pool), but is it still immutable? What does the a+=i instruction do in this time? Is the behaviour identical to my first example?

Luigi Cortese
  • 10,841
  • 6
  • 37
  • 48
  • 6
    What makes you think that all the strings are put in the pool in your first version? Only the empty string would be in the string pool... the rest would just be on the heap. And yes, all strings are immutable, regardless of whether they come from the constant pool or not. – Jon Skeet Jun 01 '15 at 14:08
  • @JonSkeet thanks for clarification... I'm a bit confused – Luigi Cortese Jun 01 '15 at 14:10

2 Answers2

4

Only String literals, or Strings you call the intern() method on are put in the String pool. Concatenation does not automagically intern a String, so your examples will be identical with regards to the String pool.

String abc = new String("abc"); //"abc" is put on the pool
abc += "def"; //"def" is put on the pool, but "abcdef" is not
String xyz = "abcdefghi".substring(0, 6).intern(); //"abcdef" is now added to the pool and returned by the intern() function
String xyz = "test"; //test is put on the pool
xyz += "ing"; //ing is put on the pool, but "testing" is not

And to expand on this, note that the String constructor does not intern (or not intern) a String automatically. The use of String literals (Strings in quotation marks in your code) is what causes the String to be on the String pool.

String abc = "abc"; //"abc" is in the pool
String def = "def"; //"def" is in the pool
String str1 = new String(abc + def); //"abcdef" is not in the pool yet
String str2 = new String("abcdef"); //"abcdef" is on the pool now

Also note that the String copy constructor is almost never used, since Strings are immutable anyway.

For more info, read the answers here, here, and here.

Community
  • 1
  • 1
Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
  • Clear! I thought there was a connection between the process of interning a string and its immutability – Luigi Cortese Jun 01 '15 at 14:15
  • `String abc = new String("abc");` goes to the pool?? My book disagrees, or I'm really confused... – Luigi Cortese Jun 01 '15 at 14:17
  • The String "abc" is a String **literal**, so yes, it goes on the pool. The constructor does not automagically intern (or **not** intern) a String. – Kevin Workman Jun 01 '15 at 14:18
  • So, what you mean is that the literal `"abc"` goes to the pool, but the variable `abc` doesn't? – Luigi Cortese Jun 01 '15 at 14:20
  • @LuigiCortese I'm not sure what you're asking. Variables themselves are never interned, so it's a bit of a non-question. I've edited my answer to include information about the constructor. – Kevin Workman Jun 01 '15 at 14:23
  • by reading answers like these http://stackoverflow.com/questions/1881922/questions-about-javas-string-pool i thought that `new String("abc");` was not going to put `"abc"` in the pool... – Luigi Cortese Jun 01 '15 at 14:28
  • @LuigiCortese The String literal "abc" is put in the pool. The String returned by the constructor is a copy of that String. – Kevin Workman Jun 01 '15 at 14:33
  • OK, that's what I meant by *the literal goes to the pool but the variable doesn't*, but said very poorly =) – Luigi Cortese Jun 01 '15 at 14:35
3

Let's consider your first example:

String a="";
for(int i=0;i++<9;)
    a+=i;

This code will be executed like this:

String a="";
a=new StringBuilder(a).append(0).toString();
a=new StringBuilder(a).append(1).toString();
a=new StringBuilder(a).append(2).toString();
a=new StringBuilder(a).append(3).toString();
a=new StringBuilder(a).append(4).toString();
a=new StringBuilder(a).append(5).toString();
a=new StringBuilder(a).append(6).toString();
a=new StringBuilder(a).append(7).toString();
a=new StringBuilder(a).append(8).toString();
a=new StringBuilder(a).append(9).toString();

So for each cycle iteration you will have a new StringBuilder created from the string (every time a new internal char[] buffer is allocated) which would be converted back to String every time. On the other hand in the second case you will effectively have

StringBuilder a=new StringBuilder("").append(0).append(1).append(2).append(3)
    .append(4).append(5).append(6).append(7).append(8).append(9);

So you will have only one StringBuilder with only one internal char[] buffer (it will not be reallocated in your case as all the appendings don't exceed the initial capacity of 16). So it's simply faster, because you have less objects and nothing is copied multiple times.

Writing a=new String("") is useless as you will have one more empty string in addition to empty string you already have (which is created and interned during the loading of your class constant pool). Aside from additional empty string (which will become unused after the first loop iteration and garbage collected) it does not differ from the first case.

Please note that it's not specified exactly in JLS how string concatenation is implemented (using implicit StringBuilder or some other technique), but usually java compilers translate it using StringBuilder.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334