9.4 StringBuilder and Advanced String Handling
1. String Immutability and Performance Issues
String is an immutable object — once created, it never changes. When you apply the + operator to a string, the existing string is not modified; instead a new String object is created every time.
// This code looks fine on the surface, but...
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // ← Creates a new String object every iteration! Very inefficient.
}
Why is this a problem? Each + operation internally creates a new String object, and the previous object becomes eligible for garbage collection. With 10,000 iterations, 10,000 objects are created and destroyed, wasting both memory and CPU.
2. StringBuilder: Mutable Strings
StringBuilder has an internal mutable buffer (char[]), so it does not create new objects when appending, inserting, or deleting characters.
StringBuilder sb = new StringBuilder(); // empty buffer (initial capacity 16)
sb.append("Hello"); // Hello
sb.append(", "); // Hello,
sb.append("Java"); // Hello, Java
sb.append("!"); // Hello, Java!
sb.insert(5, " World"); // Hello World, Java! (insert at index 5)
sb.delete(5, 11); // Hello, Java!
sb.reverse(); // !avaJ ,olleH
sb.replace(0, 5, "Bye"); // Bye, Java! (note: after reverse, be careful of position)
System.out.println(sb.toString()); // convert to String for final result
System.out.println(sb.length()); // current string length
System.out.println(sb.charAt(0)); // character at specific position
Real-World Example: Correct String Joining in Loops
// ✅ Using StringBuilder (recommended)
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 100; i++) {
sb.append(i);
if (i < 100) sb.append(", ");
}
String csv = sb.toString();
System.out.println(csv); // 1, 2, 3, ..., 100
// ✅ Also possible with String.join (Java 8+)
List<Integer> numbers = IntStream.rangeClosed(1, 100)
.boxed()
.collect(Collectors.toList());
String result = numbers.stream()
.map(String::valueOf)
.collect(Collectors.joining(", "));
Method Chaining
Most StringBuilder methods return this, enabling chaining.
String result = new StringBuilder()
.append("Name: ")
.append("Alice")
.append(", Age: ")
.append(30)
.append(" years")
.toString();
System.out.println(result); // Name: Alice, Age: 30 years
3. StringBuilder vs StringBuffer
StringBuilder | StringBuffer | |
|---|---|---|
| Thread-safe | Not safe | synchronized (thread-safe) |
| Performance | Faster | Slower (synchronization overhead) |
| When to use | Single-threaded (general use) | Multi-threaded environment |
// General situation (single-threaded) → StringBuilder
StringBuilder sb = new StringBuilder("Hello");
// When multiple threads access concurrently → StringBuffer
StringBuffer safeSb = new StringBuffer("Hello");
Practical tip: In modern Java, multi-threaded shared string handling often uses separate synchronization strategies rather than
StringBuffer. UseStringBuilderin most situations.
4. Key String Methods Reference
String s = " Hello, Java World! ";
// Whitespace handling
System.out.println(s.trim()); // "Hello, Java World!" (remove leading/trailing ASCII whitespace)
System.out.println(s.strip()); // "Hello, Java World!" (handles Unicode whitespace, Java 11+)
System.out.println(s.stripLeading()); // "Hello, Java World! " (leading only)
System.out.println(s.stripTrailing()); // " Hello, Java World!" (trailing only)
System.out.println(s.isBlank()); // false (true if only whitespace, Java 11+)
// Case
String str = "Hello Java";
System.out.println(str.toUpperCase()); // HELLO JAVA
System.out.println(str.toLowerCase()); // hello java
// Search
System.out.println(str.contains("Java")); // true
System.out.println(str.startsWith("Hello")); // true
System.out.println(str.endsWith("Java")); // true
System.out.println(str.indexOf("a")); // 7 (position of first 'a')
System.out.println(str.lastIndexOf("a")); // 9 (position of last 'a')
// Extraction
System.out.println(str.substring(6)); // "Java"
System.out.println(str.substring(0, 5)); // "Hello"
System.out.println(str.charAt(0)); // 'H'
// Replacement
System.out.println(str.replace("Java", "World")); // "Hello World"
System.out.println(str.replaceAll("\\s+", "_")); // "Hello_Java" (regex)
System.out.println("a,b,c".split(",").length); // 3
// Comparison
System.out.println("hello".equals("HELLO")); // false
System.out.println("hello".equalsIgnoreCase("HELLO")); // true
System.out.println("abc".compareTo("abd")); // -1 (lexicographic comparison)
// Java 11+
System.out.println("Java\n".repeat(3)); // "Java\n" repeated 3 times
System.out.println("hello".indent(4)); // indent by 4 spaces
// Java 15+
String name = "Alice";
int age = 30;
String formatted = "Name: %s, Age: %d".formatted(name, age);
System.out.println(formatted); // Name: Alice, Age: 30
5. String.format vs formatted vs Text Block
String name = "Alice";
double gpa = 3.75;
// Method 1: String.format (traditional)
String s1 = String.format("Name: %s, GPA: %.2f", name, gpa);
// Method 2: formatted() instance method (Java 15+, more readable)
String s2 = "Name: %s, GPA: %.2f".formatted(name, gpa);
// Method 3: Text Block + formatted (multi-line format)
String json = """
{
"name": "%s",
"gpa": %.2f
}
""".formatted(name, gpa);
System.out.println(json);
JVM String + optimization: The Java compiler automatically folds constant concatenations like "a" + "b" + "c" into "abc" at compile time. Also, Java 9+ converts simple + operations outside loops into StringBuilder automatically. However, += inside loops is NOT automatically optimized. Always use StringBuilder directly inside loops.
// Compiler automatically optimizes to StringBuilder
String s = "Hello" + ", " + "World"; // → "Hello, World" (constant folding)
// += inside a loop is NOT optimized → must use StringBuilder
for (...) {
s += something; // creates a new object every time
}