0

I am reading some book about Java 11 certification and this code really caught my attention.

public class StringCreations {
    public static void main(String[] args) {
        String hello = "hello";/*A STRING CREATED HERE*/
        for(int i=0;i<5;i++){
            hello = hello + i;/*I THINK THAT A STRING IS CREATED IN EACH ITERATION.*/
        }
        System.out.println(hello);/*6 or 11 objects created at this time?? i think is 6*/
    }    
}

The book states that this snippet created 11 objects 2 per iteration? Is this correct? I think is creating 1 object per iteration for a total of 6 objects created in total.

chiperortiz
  • 4,751
  • 9
  • 45
  • 79
  • 1
    *"The book states that this snippet created 11 objects 2 per iteration?"* 11 *objects* or 11 *strings*? Because string concatenation is implemented using `StringBuilder`s... – T.J. Crowder Feb 09 '20 at 14:53
  • 2
    Look at the byte code to see what in fact is happening – Hovercraft Full Of Eels Feb 09 '20 at 14:54
  • 2
    @HovercraftFullOfEels The bytecode in Java 11 will just have an `invokedynamic` doing the concatenation, which (by design) hides all of the details of how string concatenation is implemented. – kaya3 Feb 09 '20 at 15:11

1 Answers1

4

Fundamentally, it's not really a useful question (the book's, I mean, not yours) because it deals with the internal details of both the Java compiler and various JDK methods. But...

The book is probably referring to Java 8 or earlier (even though it's for Java 11 certification — my guess is they didn't update this example). In Java 8 and earlier, that code creates six strings (well, one of them — the one assigned to hello at the outset — is created when the class is loaded, then five created dynamically). But it also creates and throws away StringBuilder objects, one per loop iteration. Since there are five loop iterations, there are five StringBuilder objects.

6 + 5 = 11. :-)

That's no longer true in Java 9 and above, thankfully. More on that below.

You can see the StringBuilders if you compile the class (with JDK 8 or earlier), then use javap -c StringCreations to look at a rendering of the bytecode:

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String hello
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: iconst_5
       7: if_icmpge     35
      10: new           #3                  // class java/lang/StringBuilder
      13: dup
      14: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      17: aload_1
      18: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: iload_2
      22: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      25: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      28: astore_1
      29: iinc          2, 1
      32: goto          5
      35: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      38: aload_1
      39: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      42: return

The loop is offset 5 through 32. At offset 14 you can see a StringBuilder being created, then at offset 25 its toString is called (creating a new string); the loop.

The first string isn't really created by that code, it's created by loading the class (and thus its constants pool), but the five in the loop are, and of course the five StringBuilders in the loop.

Compare that with the bytecode produced by Java 13:

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #7                  // String hello
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: iconst_5
       7: if_icmpge     24
      10: aload_1
      11: iload_2
      12: invokedynamic #9,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
      17: astore_1
      18: iinc          2, 1
      21: goto          5
      24: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
      27: aload_1
      28: invokevirtual #19                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

The loop is offset 5 through offset 21, but there are no StringBuilders in sight anymore; instead, there's a call to makeConcatWithConstants. So you end up with just the six strings (the one from the constants pool, then the five created dynamically via makeConcatWithConstants).

As kaya3 points out in a comment, though, we don't know (in both cases) whether StringBuilder.append or makeConcatWithConstants converts i to a string in its implementation before returning the new string. That would mean in Java 8 it would be 16 objects (11 strings and 5 StringBuilders), and in Java 9+ 11 strings. But given that the point of makeConcatWithConstants is to "...the creation of optimized String concatenation methods...", I think we can probably assume that it doesn't create a string for i separately from creating the new string that will be its result. But really at this point we're well into the details of the Java compiler, the JVM and its JIT, etc.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    That's is what i think mate thanks for answering best regards from Venezuela. – chiperortiz Feb 09 '20 at 14:57
  • 1
    String concatenation in Java 9+ does not compile to `StringBuilder` any more; see [this question](https://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9). – kaya3 Feb 09 '20 at 14:59
  • @kaya3 - I'm astonished to find that the `javac` I used for the above is indeed Java 8. Wow. And re Java 9: Cool!!! I'll update the answer. Thanks! (I'm kicking myself, because even though I rarely do any Java anymore, I did hear about that change a few months back, just completely forgot it.) – T.J. Crowder Feb 09 '20 at 15:00
  • the book is for Java 11 certification – chiperortiz Feb 09 '20 at 15:03
  • 1
    @chiperortiz - My guess is someone didn't update it properly for the string change. :-) They probably just added new material, without flagging up this particular example is out of date. – T.J. Crowder Feb 09 '20 at 15:04
  • How many objects is creating for Java >9? – chiperortiz Feb 09 '20 at 15:05
  • 1
    Yeah, I found out the same way (-: I think the question is not so simple for Java 9+ though, because it's a very-undocumented implementation detail what actually happens at runtime. If the answer of 11 is up-to-date and isn't followed by a long discussion about implementation details, it's possibly counting the conversion of `i` to a string (which is likely to happen, but not guaranteed by the spec), or there's some funny business with the dynamically-invoked string concatenation method taking varargs as an array which needs to be instantiated (but I don't think this should happen). – kaya3 Feb 09 '20 at 15:07
  • @kaya3 - And with `makeConcatWithConstants` we have the issue that it accepts `Object` varargs...so `i` might be converted to `Integer` before being passed in and converted to `String` and then added to... :-) (`Integer` will cache those but still.) Fun fun fun... – T.J. Crowder Feb 09 '20 at 15:16
  • 1
    From my understanding, `makeConcatWithConstants` is responsible for synthesizing a method (presumably at runtime) which will be called to do the concatenation; it doesn't do the concatenation itself. I presume that the synthetic method won't have varargs, but because nothing about this is specified, it's rather difficult to say for certain. – kaya3 Feb 09 '20 at 15:19
  • @kaya3 - Yeah, it would be nuts to put that whole infrastructure there but then still be effectively using `Integer.valueOf(i)` to concatenate an `int`. – T.J. Crowder Feb 09 '20 at 15:20
  • 2
    I think you are probably right that the textbook just has an out-of-date question in it. I think it's practically unanswerable in Java 11. Even in Java 8 I think it's a mess, because StringBuilder internally allocates arrays, and may have to reallocate them to increase capacity when `append` is called; shouldn't the arrays be counted? – kaya3 Feb 09 '20 at 15:23