Key Takeaways
• String literals are stored within a dedicated area of the heap area.
• String instantiation can determine how it can be equated to other Strings.
• The StringBuffer and StringBuilder classes offer methods that allow a sequence of characters to be handled as a mutable Object.
Introduction to java.lang.String
The String class is used in Java to create an immutable sequence of unicode characters; each String Object is immutable and final, and therefore thread-safe. The following code sample demonstrates how a String can be instantiated with the new
operator or as a literal:
String stringOne = new String("Hello");
String stringTwo = "World";
The memory allocation of String Objects is determined by how they are instantiated. The new
operator will assign the String into the heap memory, however, a literal String will be stored into the String pool.
String pool
The String pool is a dedicated area within the heap memory for storing String Objects. Before a String literal is created, the JVM will scan the String pool to check if the String value already exists; if the String already exists within the String pool, the String literal will point to the String pool Object.
The String pool is demonstrated using the below example:
String stringOne = new String("Hello");
String stringTwo = "World";
String stringThree = "World";
• When stringOne
is created, the String Object is created in the heap space.
• When stringTwo
is created, the String is assigned to the String pool.
• When stringThree
is created, the literal will point the existing String in the String pool.
The immutable nature of a String Object means new Objects are created when the value of String is updated. If stringTwo
and stringThree
were assigned to different values, the JVM will create two new Objects providing the new values are not found in the String pool. The String pool will retain the memory address of the value "World", however it will remain unreferenced in the pool:
String stringTwo = "World";
String stringThree = "World";
stringTwo = "Hello";
stringThree = "Gosling";
Interning Strings
intern()
is a public method of the String class that is used to assign a String Object into the String pool. All literal instantiations of a String object call the intern() method by default. When a String is created with the new
operator, the intern()
method is not called and the String is only stored in the heap area. Calling intern()
on a String Object that is instantiated with the new
operator will provide additional storage of the String into the String pool. The reference to the String object will now point to the String pool. As a result, twoString can be similarly compared using the ==
operator as opposed to the .equals()
method.
The below example demonstrates the use of intern()
:
• stringOne is created once: in heap memory
• stringTwo is created twice: in the heap and String pool.
String stringOne = new String("Hello");
String stringTwo = new String("World").intern();
Comparing Strings
String Objects can be compared with either ==
or .equals()
. ==
is a reference comparator and will compare the memory locations of the two Strings. .equals()
is a content comparator and will compare the value of the two Strings.
By using the intern()
method, the String Object will evaluate to true when using ==
to compare references with a String literal of the same value. While the ==
method is a more time efficient comparator, the equality of two String Objects is dependent upon their location reference and not their content. As a result, two Strings with the same value may not evaluate to true if one was created in the heap area.
The code sample below demonstrates how different String Objects can be compared using their memory address and value:
String stringOne = "Hello";
String stringTwo = new String("Hello");
String stringThree = new String("Hello").intern();
System.out.println(stringOne == stringTwo); //false
System.out.println(stringOne == stringThree); //true
System.out.println(stringOne.equals(stringTwo)); //true
System.out.println(stringOne.equals(stringThree)); //true
System.out.println(stringTwo == stringThree); //false
StringBuffer and StringBuilder
The StringBuffer and StringBuilder classes provide support to using Strings as mutable Objects. StringBuffer and StringBuilder Objects are created within the heap area.
The StringBuffer and StringBuilder classes differ by their thread safety. StringBuffer Objects are accessed through synchronized methods and can therefore only be accessed by a single thread at a single time. The StringBuilder Object is not thread-safe and can be accessed simultaneously by different threads. The StringBuilder class is often preferred due to its latency performance over StringBuffer.
Both StringBuffer and StringBuilder Objects provide functions that enable a String to be transformed and accessed in memory:
• append(String)
to concatenate String Objects onto the StringBuffer/Builder
• insert(int, String)
to insert the specified String within the StringBuffer/Builder at the int
position.
• reverse()
to reverse a StringBuffer/Builder.
• delete(int, int)
to delete the characters from the start and end index defined by the two arguments.
An additional benefit to using the StringBuffer and StringBuilder aligns to their resemblance of the builder pattern to enhance code readability:
public String createString(stringArg){
StringBuilder sb = new StringBuilder("hello");
sb.append(stringArg); //add argument to sb
sb.insert(3,"__"); //add "__" at third position
sb.deleteCharAt(sb.length()-1); //remove the final char
return sb.toString(); //return sb as a String object
}
Conclusion
The String class is uniquely handled by the JVM with an optimized pipeline and storage. The immutability of a String means new Objects are created when the String value is modified. A String Object can be instantiated into the heap area which effectively alters how the String Object is equated to other String Objects. Lastly, the JVM offers StringBuffer and StringBuilder classes to provide functionality for String Objects as a mutable Object.