You are currently viewing String vs StringBuilder
java_logo

String vs StringBuilder

1. String

Strings are the building blocks for manipulating textual information and representing sequences of characters. In Java, strings are instances of the String class. Conventionally, strings can be created using the new keyword, but Java is designed to make string creation seamless. You can create strings without explicitly using new, making the code cleaner and more intuitive.

// Using new keyword
String strWithNew = new String("Hello, World!");

// Without new keyword (preferred approach)
String strWithoutNew = "Hello, Java!";

The second approach is more common and is known as string literal creation. This method is favored for its simplicity and readability.

Strings in Java implement the CharSequence interface, providing a uniform way to work with character sequences. This interface defines methods for querying and extracting information from sequences of characters, reinforcing the versatility of strings in Java.

1.1. Concatenation

One of the most intriguing aspects of Java strings is their concatenation capabilities. Concatenation is the process of combining strings, and in Java, the + operator is used for this purpose. However, the behavior of the + operator depends on the types of the operands involved.

1.1.1. Concatenation Rules

  • If both operands are numeric, + performs addition.
  • If either operand is a string, + performs concatenation.
  • The expression is evaluated from left to right.
// Numeric addition
String result1 = 1 + 2 + "C";   // Result: "3C"

// String concatenation
String result2 = "C" + 2 + 1;   // Result: "C21"

In the first example, the addition is performed before concatenating with the string “C”. In the second example, the string “C” is concatenated with the subsequent numeric values.

The += operator provides a concise way to perform concatenation and assign the result back to a variable.

String text = "Hello, ";
text += "Java!";

In this example, the += operator appends “Java!” to the existing string, making it “Hello, Java!”.

1.2. Immutability

Strings hold a unique position due to their immutability. Once a String object is created, it remains fixed and unchangeable. You can think of it as a sealed storage box with a predetermined content. While this immutability offers advantages in terms of consistency and optimal memory usage, it comes at the cost of zero flexibility.

Immutability in Java strings means that once a String object is instantiated, its content cannot be modified. Any operation that seems to alter a string actually creates a new string object with the desired changes, leaving the original string intact.

public class ImmutabilityExample {
    public static void main(String[] args) {
        String originalString = "Java";

        originalString.concat(" is amazing!");

        System.out.println("Original String: " + originalString);

        String modifiedString = originalString.concat(" is amazing!");

        System.out.println("Modified String: " + modifiedString);
    }
}

output:

Original String: Java
Modified String: Java is amazing!

In this example, the concat method is used to append ” is amazing!” to the original string. However, notice that the original string remains unchanged. This is because the result of the concat operation is not assigned back to the original string. Instead, a new string (modifiedString) is created to hold the modified content.

1.3. String Methods

Java’s String has a plethora of methods that empower developers to manipulate and analyze text efficiently. In this exploration, we’ll delve into some of the essential string methods

1. length()

The length() method returns the length of a string, representing the number of characters in the sequence.

String message = "Hello, Java!";
int length = message.length(); // length is 12

The length() method in Java, unlike indexing or position counting, adheres to conventional counting starting from 1. When you invoke the length() method on a String object, it returns the total number of characters in the string, with the count beginning at 1 for the first character and incrementing by 1 for each subsequent character.

2. charAt()

charAt(int index) retrieves the character at the specified index in the string.

String word = "Java";
char firstChar = word.charAt(0); // firstChar is 'J'
char outOfBoundsChar = word.charAt(10); // StringIndexOutOfBoundsException

When using the charAt(int index) method in Java, it’s essential to remember that the index is zero-based. Attempting to access an index that is out of the bounds of the string will result in a StringIndexOutOfBoundsException.

3. indexOf()

indexOf(String str) returns the index of the first occurrence of the specified substring.

indexOf(String str, int fromIndex) returns the index of the first occurrence of the specified substring, starting from the given fromIndex

String sentence = "Java is amazing!";
int index = sentence.indexOf("is"); // index is 5
int index = sentence.indexOf("a", 5); // index is 8

4. substring()

substring(int beginIndex, int endIndex) extracts a portion of a string, and it is zero-based. It returns a new string that consists of characters from the beginIndex to endIndex - 1. The second parameter, endIndex, is exclusive. The endIndex parameter is optional. If you omit it, the substring will include all characters from beginIndex to the end of the string.

substring(int beginIndex) extracts a portion of the string between the specified indices.

        String text = "Programming is fun!";
        String snippet = text.substring(15, 18); // "fun"
        String substring = text.substring(5); // "amming is fun!"
        String substring2 = text.substring(5, 19); // "amming is fun!"
        String emptyString = text.substring(3, 3); // ""
        String wrong = text.substring(3, 2); // StringIndexOutOfBoundsException

5. toLowerCase() and toUpperCase()

These methods convert the string to lowercase and uppercase, respectively.

String phrase = "Hello, World!";
String lowercase = phrase.toLowerCase(); // "hello, world!"
String uppercase = phrase.toUpperCase(); // "HELLO, WORLD!"

6. equals() and equalsIgnoreCase()

These methods compare strings for equality, considering case sensitivity.

String str1 = "Java";
String str2 = "java";
boolean isEqual = str1.equals(str2); //  false
boolean isEqualIgnoreCase = str1.equalsIgnoreCase(str2); // true

7. startsWith() and endsWith()

These methods check if a string starts or ends with a specified prefix or suffix. The check is case-sensitive

String filename = "example.txt";
boolean startsWith = filename.startsWith("ex"); // true
boolean startsWithCapitalCase = filename.startsWith("Ex"); // false
boolean endsWith = filename.endsWith(".txt"); //  true

8. replace()

replace(char oldChar, char newChar) replaces occurrences of a specified character.

replace(CharSequence target, CharSequence replacement) replaces all occurrences of a specified target sequence with another sequence.

String phrase = "Java is cool!";
String updatedPhrase = phrase.replace('c', 'C'); // "Java is Cool!"
String updatedPhrase2 = phrase.replace("cool", "fun"); // "Java is fun!"

9. contains()

contains(CharSequence sequence) checks if the string contains a specified sequence of characters.

String sentence = "Programming is fascinating!";
boolean containsWord = sentence.contains("is"); // containsWord is true

10. trim(), strip(), stripLeading(), and stripTrailing()

trim() and strip() remove leading and trailing whitespaces from a string. The stip() method is new since Java 11. It does everything that trim() does, but it supports Unicode

String spacedString = "   Hello, Java!   ";
String trimmed = spacedString.trim(); // "Hello, Java!"
String stripped = spacedString.strip(); // "Hello, Java!"
String leadingStripped = spacedString.stripLeading(); // "Hello, Java!   "
String trailingStripped = spacedString.stripTrailing(); // "   Hello, Java!"

1.4. Method Chaining

Method chaining in Java allows you to invoke multiple methods on an object in a single line, with each method returning an object that supports the subsequent method call. This approach leads to more concise and readable code.

public class MethodChainingExample {
    public static void main(String[] args) {
        String text = "  Java is amazing!  ";

        String result = text.trim().toUpperCase().replace("AMAZING", "incredible").substring(5, 18);

        System.out.println("Original Text: " + text);
        System.out.println("Modified Result: " + result);
    }
}

In this example, we start with the original string ” Java is amazing! “. We then chain several string methods together:

  1. trim(): Removes leading and trailing whitespaces.
  2. toUpperCase(): Converts the entire string to uppercase.
  3. replace("AMAZING", "incredible"): Replaces the specified substring “AMAZING” with “incredible”.
  4. substring(5, 18): Extracts a substring from index 5 to 17 (exclusive).

The output will be:

Original Text:   Java is amazing!  
Modified Result: IS incredible

2. StringBuilder

The StringBuilder class in Java provides a mutable sequence of characters, allowing for efficient modifications without creating new objects for each change. This is particularly useful when you need to concatenate or modify strings in a loop, where the immutability of String objects might lead to inefficiency.

public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder("Java is");

        for (int i = 0; i < 5; i++) {
            stringBuilder.append(" powerful"); // Append " powerful" in each iteration
        }

        String result = stringBuilder.toString();
        System.out.println("Result: " + result);
    }
}

output:

Result: Java is powerful powerful powerful powerful powerful

In this example, a StringBuilder is initially created with the content “Java is”. Inside the loop, the append method is used to concatenate ” powerful” to the existing content in each iteration. Unlike using String concatenation in a loop, where new objects would be created at each step, StringBuilder allows for efficient modifications in-place.

2.1. Mutability and Chaining

Unlike String, which is immutable, StringBuilder allows modifications in-place and returns a reference to itself after each method call. Here’s an example demonstrating this difference:

public class MutabilityChainingExample {
    public static void main(String[] args) {
        String stringResult = "Java"
                .concat(" is")
                .concat(" powerful");
        stringResult.concat(" and versatile");

        StringBuilder stringBuilder = new StringBuilder("Java");
        stringBuilder
                .append(" is")
                .append(" powerful")
                .append(" and versatile");

        System.out.println("String Result: " + stringResult);
        System.out.println("StringBuilder Result: " + stringBuilder);
    }
}

output

String Result: Java is powerful
StringBuilder Result: Java is powerful and versatile
  1. Using String for concatenation (immutable):
    • The concat method is used to concatenate substrings in a chained manner.
    • However, the result of the last concat call (stringResult.concat(" and versatile");) is not assigned back to stringResult. String objects are immutable, and the concat method returns a new String object, which is discarded in this case.
  2. Using StringBuilder for modifications (mutable):
    • The append method is used to append substrings to the StringBuilder in a chained manner.
    • The modifications made by the append calls directly affect the state of the existing StringBuilder object.

2.2. StringBuilder Methods

1. length(), charAt(), indexOf() and substring()

These 4 methods work exactly the same as in the String class

        StringBuilder stringBuilder = new StringBuilder("Hello, World!");
        char charAtIndex = stringBuilder.charAt(2); // l
        int indexOfJava = stringBuilder.indexOf("World"); // 7
        int length = stringBuilder.length(); // 13
        String subString = stringBuilder.substring(3, 8); // lo, W

2. append()

Appends the specified string to the end of the StringBuilder object.

StringBuilder stringBuilder = new StringBuilder("Java");
stringBuilder.append(" is powerful."); // "Java is powerful."

3. insert()

Inserts the specified string into the StringBuilder object at the specified offset. The insert method takes two parameters:

  • offset: The index at which the specified string will be inserted.
  • str: The string to be inserted at the specified offset.
StringBuilder stringBuilder = new StringBuilder("Java is versatile.");
stringBuilder.insert(8, "powerful and "); // Java is powerful and versatile.

4. delete() and deleteCharAt()

delete(int start, int end) removes characters from the StringBuilder object from the specified start index to the end index.

deleteCharAt(int index): Removes the character at the specified index from the StringBuilder object.

StringBuilder stringBuilder = new StringBuilder("Java is powerful.");
stringBuilder.delete(7, 16); // Java is.
stringBuilder.deleteCharAt(3); // Jav is.

5. replace()

replace(int start, int end, String str): Replaces the characters from the start index to the end index with the specified string.

StringBuilder stringBuilder = new StringBuilder("Java is powerful.");
stringBuilder.replace(8, 16, "versatile"); // Java is versatile.

6. reverse()

Reverses the characters in the StringBuilder object.

StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.reverse(); // olleH

7. toString()

Converts a StringBuilder into a String

3. Understanding Equality

3.1. == Operator

  • Compares references, not content.
  • Checks if two objects refer to the same memory location.

3.2. equals Method

  • Compares the content of objects.
  • Needs to be explicitly overridden in custom classes for meaningful content-based comparison.

3.3. Equality with StringBuilder

== checks if two StringBuilder references point to the same object.

StringBuilder sb1 = new StringBuilder("Hello");
StringBuilder sb2 = new StringBuilder("Hello");
StringBuilder sb3 = sb1.append(" World");

System.out.println(sb1 == sb2);  // false
System.out.println(sb1 == sb3);  // true

StringBuilder does not override the equals method for content-based comparison. To compare content, you can convert StringBuilder to String using toString and then use equals.

StringBuilder sb1 = new StringBuilder("Hello");
StringBuilder sb2 = new StringBuilder("Hello");
System.out.println(sb1.toString().equals(sb2.toString()));  // true
System.out.println(sb1.equals(sb2));  // false

3.4. Equality with String

== checks if two String references point to the same object.

String str1 = "Hello";
String str2 = "Hello";
String str3 = str1;
String str4 = str3.concat(" World");
System.out.println(str1 == str2);  // true
System.out.println(str1 == str3);  // true
System.out.println(str4 == str3);  // false

The == comparison between str1 and str2 returns true because both reference the same interned String object in the string pool (see next chapter). In this case, the compiler recognizes that the content of the two string literals is the same (“Hello”), so it uses the same String object for both. It’s important to note that this behavior is specific to string literals and the interning mechanism. When you create strings using the new keyword or concatenate strings at runtime, a new object is created, and == would then check for reference equality.

The concat method creates a new String object (due to the immutability of String), and str4 ends up referring to a different object than str3. Even though the content may be the same, == checks for reference equality, and in this case, the references are different.

equals method is overridden in the String class for content-based comparison.

String str1 = "Hello";
String str2 = "Hello World";
String str3 = str1;
String str4 = str3.concat(" World");
System.out.println(str1.equals(str2));  // false
System.out.println(str1.equals(str3));  // true
System.out.println(str4.equals(str2));  // true

  • str1.equals(str2) is false because the content is different.
  • str1.equals(str3) is true because they point to the same object with the same content.
  • str4.equals(str2) is true because their content is the same after the concatenation.

4. The String Pool

In Java, the String Pool is a special area in the Java Virtual Machine (JVM) memory where string literals and constants are stored to optimize memory usage. The String Pool helps conserve memory by reusing common strings instead of creating new objects for identical string literals.

String str1 = "Hello";       
String str2 = "Hello"; 

System.out.println(str1 == str2);  // true

str1 is a literal that gets placed into the String Pool. str2 reuses the same literal from the String Pool. So str1 and str2 both reference the same literal from the String Pool

String original = "Trim me!";
String trimmed = original.trim();  // Creates a new string

System.out.println(original == trimmed);  // false 

The trim() method returns a new string with leading and trailing whitespaces removed. Since strings are immutable, the trim() method does not modify the original string but creates a new one.

String literal = "Hello";
String newString = new String("Hello");  

System.out.println(literal == newString);  // false 

Creating a string with the new keyword explicitly results in a new object and does not utilize the String Pool. Therefore, comparing it with a literal using == will yield false.

String str3 = new String("Hello").intern(); 
String literal = "Hello";

System.out.println(literal == str3);  // true

The intern() method can be used to add a string to the String Pool explicitly, even if it is created with the new keyword. After interning, the strings reference the same object in the String Pool.