Exploring the String Class
1st Jun 2020 by Aneesh Mistry

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.

String pool diagram

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";

String pool diagram


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();

String operator diagram


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.


Share this post