38

My friend sent me a question he saw in one mock exam for the Java certification about string objects:

String makeStrings(){
    String s = "HI";
    s = s + "5";
    s = s.substring(0,1);
    s = s.toLowerCase();
    return s.toString();
}

How many string objects will be created when this method is invoked? The correct answer the exam gave was 3. But I think it's five.

  1. "HI"
  2. "5"
  3. "HI5"
  4. "H"
  5. "h"

Am I wrong?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mauro M
  • 669
  • 1
  • 8
  • 25
  • 14
    I suspect the difference is that "HI" and "5" are already in the string pool, so they're not created on each method invocation. – Jon Skeet Jul 27 '13 at 13:19
  • 1
    @GrijeshChauhan I made a [post](http://meta.stackexchange.com/questions/190688/determining-whether-a-post-should-be-marked-as-a-duplicate#190689) on SO meta. Instead of closing this post, I think we should vote to close the other one. Perhaps I'm biased, but I think these answers are better... – Steve P. Jul 27 '13 at 18:56
  • 1
    @SteveP. I voted it to close already with duplicate option... – Grijesh Chauhan Jul 27 '13 at 18:57
  • 1
    This should be reopened and the other one should be marked as a duplicate. The answers in this question are more explanatory... – Steve P. Jul 29 '13 at 00:05
  • 1
    @EJP I don't think that it's a duplicate of that question. Nonetheless, some redundancy between questions should be expected. It's not necessarily a bad thing to have marginally different questions and keep them both open... – Steve P. Jul 29 '13 at 08:16
  • Looks like 3 to me, "HI5", "H", "h". "HI" and "5" are constants, so a new string isn't created for those in this method. – Chase Jul 29 '13 at 08:17
  • @SteveP. These questions may have different preconditions and different numerical answers, but the underlying answer is always the same: the string literals and compile-time concatenations don't create new objects; the `new String()` calls do. Unless you think it is desirable to have a separate question every time the preconditions or the numerical answer is different, e.g. 1,2,3,4,5,... *ad infinitum* I really cannot see the point of your comment. – user207421 Jul 29 '13 at 09:43
  • 1
    @EJP Well, the other questions don't contain anything about `substring()`, `toLowerCase() and `toString()` is what I meant. – Steve P. Jul 29 '13 at 15:45
  • Again, I can't vote to reopen again, but it's overtly obvious that this is not a duplicate question. In fact the other question is a subset of this question. This question includes `substring()`,`toLowerCase()`, and `toString()` which the other question does not include. I do not know why this was closed... – Steve P. Jul 30 '13 at 21:22
  • Don't forget that String is typically two objects, not one. There is the `String` and the `char[]` (possibly a `byte[]`) that it wraps. – Peter Lawrey Jul 31 '13 at 05:07

3 Answers3

46
String makeStrings() {
    String s = "HI";           //String literal
    s = s + "5";               //concatenation creates new String object (1)
    s = s.substring(0,1);      //creates new String object (2)
    s = s.toLowerCase();       //creates new String object (3)
    return s.toString();       //returns already defined String
}

With respect to the concatenation, when creating a new String,JVM uses StringBuilder, ie:

s = new StringBuilder(s).append("5").toString(); 

toString() for a StringBuilder is:

public String toString() {
    return new String(value, 0, count); //so a new String is created
}

substring creates a new String object unless the entire String is indexed:

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex)
    }

    return ((beginIndex == 0) && (endIndex == count)) ? this :
           new String(offset + beginIndex, endIndex - beginIndex, value);
}

toString() does NOT create a new String:

public String toString()
{
   return this;
}

toLowerCase() is a pretty long method, but suffice it to say that if the String is not already in all lowercase, it will return a new String.

Given that the provided answer is 3, as Jon Skeet suggested, we can assume that both of the String literals are already in the String pool. For more information about when Strings are added to the pool, see Questions about Java's String pool.

Community
  • 1
  • 1
Steve P.
  • 14,489
  • 8
  • 42
  • 72
  • 1
    Substring creates a new string object, toString doesn't – Joni Jul 27 '13 at 13:22
  • `substring` will create an object here. It returns the same string only when your indices are at boundaries of original string - `[0, s.length() - 1]` – Rohit Jain Jul 27 '13 at 13:23
  • @Joni, I didn't realize that it was actually calling `toString()` on a String. I made the necessary changes (for both). Thank you. – Steve P. Jul 27 '13 at 13:27
  • With your explanation, I see 4 strings created, instead of 3, as the asker said: string literal, concatenation, substring and toLowerCase. – Guillermo Gutiérrez Jul 30 '13 at 22:56
  • 1
    @guillegr123 Given that the answer provided by the OP was `3`, one can deduce that the string literal was already added to the String pool. – Steve P. Jul 30 '13 at 23:01
  • @SteveP. i just accepted it, but i dont understand why the "5" isnt considered a new String object, theres nothing more to the question than what i posted, how can i assume that the 5 is already in the pool? – Mauro M Aug 01 '13 at 14:00
  • @MauroMazzucco Thank you. You can't for no reason assume that it's already in the pool; however, since the answer provided was `3`, we **CAN** assume that the `String` was already in the pool--it's the only way the answer makes sense. – Steve P. Aug 01 '13 at 15:36
  • From Java 7 onwards, Strings are only added to the String literal pool as they are used for the first time. While your answer is correct for earlier versions if you ignore the Strings added to the String literal pool, that is a big assumption. Your answer is more accurate if the method is called multiple times although if it is called enough in Java 8 the String objects could be unpacked on to the heap with Escape analysis and might not exist at all even the one which get returned in the method is inlined. – Peter Lawrey Oct 06 '15 at 15:21
13

s = s + "5";

Is translated to:

String s = new StringBuilder(s).append("5").toString();

For now, one Object is created.


s = s.substring(0,1); 

Creates a new String.


s = s.toLowerCase();

Creates a new Object.


return s.toString(); 

Doesn't create a String, it returns an already created one.

Maroun
  • 94,125
  • 30
  • 188
  • 241
9

Some of the other answers do make sense, but what about the string literal?

String s = "HI";

For the string literals, when a .java file is compiled into a .class file, any string literals are noted in a special way, just as all constants are. When a class is loaded (note that loading happens prior to initialization), the JVM goes through the code for the class and looks for string literals.

When it finds one, it checks to see if an equivalent String is already referenced from the heap. If not, it creates a String instance on the heap and stores a reference to that object in the constant table

Once a reference is made to that string object, any references to that string literal throughout your program are simply replaced with the reference to the object referenced from the string literal pool.

Hence there should be four Java objects, although when the same method is called again and again then there would only be three objects as in the application the string literal pool contains the literal "HI".

Also, for more information on why new objects are created when the above method blocks are exectued we can also check the hash codes which are different for different strings (String being immutable.)

  public static void main(String[] args)
  {
      NumberOfString str = new NumberOfString();
      String s = str.makeStrings();
      System.out.println(s.hashCode());
  }

  public String makeStrings()
  {
      String s = "HI";
      System.out.println(s.hashCode());
      s = s + "5";
      System.out.println(s.hashCode());
      s = s.substring(0, 1);
      System.out.println(s.hashCode());
      s = s.toLowerCase();
      System.out.println(s.hashCode());
      return s.toString();
  }

You get the following output:

2305
71508
72
104
104

Should we not count in the String literal object in the above example?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Abhishek
  • 271
  • 3
  • 6
  • Based on the answer given in the OP's question, we can assume that they are already in the String pool. Nonetheless, good answer (+1). – Steve P. Jul 29 '13 at 09:12
  • 1
    The original question states "How many String objects will be created when this method is invoked?", inferring that the class has already been loaded and the method is being called from another piece of code. Based on your explanation, the String literal is created when the class is loaded, not specifically when this method is invoked, and therefore would not count toward the total in question. – Pawn Jul 30 '13 at 18:20