Strings in Java — Immutability, Methods, StringBuilder
2255 words
11 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Strings in Java — Immutability, Methods, StringBuilder ## 🎯 Learning Objectives By the end of this topic, you will be able to: - Explain String immutability and its performance implications - Use common String methods: `length()`, `charAt()`, `substring()`, `indexOf()`, `equals()`, `compareTo()` - Differentiate b...

Strings in Java — Immutability, Methods, StringBuilder
🎯 Learning Objectives
By the end of this topic, you will be able to:
- Explain String immutability and its performance implications
- Use common String methods:
length(),charAt(),substring(),indexOf(),equals(),compareTo() - Differentiate between String, StringBuilder, and StringBuffer
- Choose the right string class for different scenarios
- Understand the String pool and interning
📋 Prerequisites
- Arrays (04-arrays.md): Strings are essentially
char[]internally - Memory Model (week01/03-memory-model.md): Objects on heap, references on stack
1. Intuition: Why Strings Are Special
1.1 What Problem Does This Solve?
Strings are the most used data type in nearly every program — names, addresses, text, commands, error messages. Java treats
String with special status:- It's the only class with its own operator (
+for concatenation) - It's the only class with string literal syntax (
"hello") - It has a dedicated string pool for memory optimization
1.2 Mental Model: An Envelope with a Photo Inside
Imagine a sealed envelope with a photo inside. You can:
- Look at the photo (read the string)
- Remove the photo and put a different one in (create a new string)
- But you cannot modify the photo itself — it's permanently sealed This is immutability: once created, a String's content never changes.
2. String Immutability
2.1 What Does Immutable Mean?
javaString s = "Hello"; s = s + " World"; // Does NOT modify "Hello" — creates a NEW string
After this code:
- The original
"Hello"still exists in memory (unmodified) - A new string
"Hello World"is created snow references the new string (Diagram)
2.2 Why Immutability?
- Thread safety: Strings can be safely shared across threads without synchronization
- Caching: Hash codes can be cached (String caches its
hashCode()after first computation) - Security: Strings used in class loading, network connections, file paths can't be altered
- String pool: Immutability enables safe sharing of string literals
2.3 The Cost of Immutability
Immutability means every modification creates a new object:
javaString s = ""; for (int i = 0; i < 1000; i++) { s = s + i; // Creates a NEW string each iteration — O(n²) time! }
This loop creates 1000+ string objects and copies all previous content each time. Never do this in a loop — use
StringBuilder instead.3. The String Pool
3.1 Interning Strings
Java maintains a string pool (in the method area / heap) of unique string literals:
javaString s1 = "Hello"; // Created in pool String s2 = "Hello"; // Reuses pool reference String s3 = new String("Hello"); // Forces new heap object (NOT in pool) System.out.println(s1 == s2); // true — same object in pool System.out.println(s1 == s3); // false — different objects System.out.println(s1.equals(s3)); // true — same content
(Diagram)
3.2 Explicit Interning
javaString s = new String("Hello"); String interned = s.intern(); // Returns pool reference System.out.println(interned == "Hello"); // true
4. Common String Methods
4.1 Inspection Methods
| Method | Description | Example | Result |
|---|---|---|---|
length() | Number of characters | "Hello".length() | 5 |
charAt(i) | Character at index i | "Hello".charAt(1) | 'e' |
isEmpty() | Is length 0? | "".isEmpty() | true |
indexOf(ch) | First index of character | "Hello".indexOf('l') | 2 |
lastIndexOf(ch) | Last index of character | "Hello".lastIndexOf('l') | 3 |
contains(s) | Contains substring? | "Hello".contains("ell") | true |
startsWith(prefix) | Starts with prefix? | "Hello".startsWith("He") | true |
endsWith(suffix) | Ends with suffix? | "Hello".endsWith("lo") | true |
4.2 Comparison Methods
| Method | Description | Example | Result |
|---|---|---|---|
equals(s) | Content comparison | "Hi".equals("Hi") | true |
equalsIgnoreCase(s) | Case-insensitive | "Hi".equalsIgnoreCase("hi") | true |
compareTo(s) | Lexicographic comparison | "A".compareTo("B") | Negative |
compareToIgnoreCase(s) | Case-insensitive compare | "a".compareToIgnoreCase("A") | 0 |
compareTo returns:- Negative if this string comes before the argument
- Zero if they're equal
- Positive if this string comes after
javaSystem.out.println("apple".compareTo("banana")); // Negative (<0) System.out.println("hello".compareTo("hello")); // 0 System.out.println("zebra".compareTo("apple")); // Positive (>0)
4.3 Manipulation Methods (Return New Strings)
| Method | Description | Example | Result |
|---|---|---|---|
substring(start, end) | Extract substring | "Hello".substring(1, 4) | "ell" |
substring(start) | Substring from start | "Hello".substring(2) | "llo" |
concat(s) | Append string | "Hi".concat(" there") | "Hi there" |
replace(old, new) | Replace characters | "Hello".replace('l', 'w') | "Hewwo" |
toLowerCase() | Convert to lowercase | "Hello".toLowerCase() | "hello" |
toUpperCase() | Convert to uppercase | "Hello".toUpperCase() | "HELLO" |
trim() | Remove leading/trailing whitespace | " Hi ".trim() | "Hi" |
strip() | Unicode-aware trim (Java 11+) | " Hi ".strip() | "Hi" |
join(delimiter, parts) | Join strings | String.join("-", "a", "b", "c") | "a-b-c" |
4.4 Searching Methods
| Method | Description | Example | Result |
|---|---|---|---|
indexOf(str) | First occurrence | "Hello".indexOf("ll") | 2 |
lastIndexOf(str) | Last occurrence | "Hello llo".lastIndexOf("ll") | 6 |
matches(regex) | Regex match | "abc123".matches("\\w+") | true |
split(regex) | Split by delimiter | "a,b,c".split(",") | ["a","b","c"] |
4.5 Converting to/from Arrays
java// String → char array String s = "Hello"; char[] chars = s.toCharArray(); // String → byte array (for encoding) byte[] bytes = s.getBytes(); // Uses default charset // char array → String String t = new String(new char[]{'H', 'i'}); // String → int/parse int num = Integer.parseInt("123"); double d = Double.parseDouble("3.14");
5. StringBuilder and StringBuffer
5.1 The Problem They Solve
String concatenation in loops is extremely inefficient:
java// BAD — O(n²) time, creates n intermediate strings String s = ""; for (int i = 0; i < 10000; i++) { s += i; // Each += copies the entire string! } // GOOD — O(n) time, mutable buffer StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append(i); // Appends without copying the whole thing } String result = sb.toString();
5.2 StringBuilder vs StringBuffer
| Feature | StringBuilder | StringBuffer |
|---|---|---|
| Thread safety | Not synchronized (faster) | Synchronized (thread-safe) |
| Speed | Faster | Slower (due to synchronization) |
| When to use | Single-threaded | Multi-threaded (rare) |
In practice: Use
StringBuilder almost always. StringBuffer was the original Java 1.0 version; StringBuilder (Java 5+) is its faster replacement.5.3 StringBuilder Methods
javaStringBuilder sb = new StringBuilder("Hello"); sb.append(" World"); // Append: "Hello World" sb.insert(5, " Dear"); // Insert at index 5: "Hello Dear World" sb.replace(6, 10, "Friend"); // Replace range: "Hello Friend World" sb.delete(5, 7); // Delete range: "Hello iend World" sb.reverse(); // Reverse: "dlroW dneiF olleH" sb.setCharAt(0, 'h'); // Set single char: "h..." sb.length(); // Length sb.toString(); // Convert to String
5.4 Performance Comparison
java// Method 1: String concatenation (BAD) long start = System.nanoTime(); String s = ""; for (int i = 0; i < 10000; i++) { s += i; } long end = System.nanoTime(); System.out.println("String: " + (end - start) / 1_000_000 + " ms"); // Method 2: StringBuilder (GOOD) start = System.nanoTime(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append(i); } end = System.nanoTime(); System.out.println("StringBuilder: " + (end - start) / 1_000_000 + " ms"); // Results: StringBuilder is typically 100-1000x faster for large loops!
6. String Comparison: == vs equals()
This is the #1 String mistake in Java:
javaString s1 = "Hello"; String s2 = "Hello"; String s3 = new String("Hello"); System.out.println(s1 == s2); // true (same pool reference) System.out.println(s1 == s3); // false (different references) System.out.println(s1.equals(s3)); // true (same content) // User input always creates new strings: Scanner sc = new Scanner(System.in); String input = sc.nextLine(); // e.g., "Hello" System.out.println(s1 == input); // false! Always check equals() System.out.println(s1.equals(input)); // true
Rule of thumb: Use
.equals() for content comparison. Only use == if you intentionally want to check reference identity.7. Java vs Python: Strings
| Feature | Java | Python |
|---|---|---|
| Immutable | Yes | Yes |
| String pool | Yes (literals) | Yes (interned automatically) |
| Concatenation | + operator | + operator |
| Repetition | No operator | "a" * 3 → "aaa" |
| Format | String.format() or %s | f"{name}" or .format() |
| StringBuilder | StringBuilder, StringBuffer | Not needed (strings immutable but join is fast) |
| Substring | s.substring(1, 4) | s[1:4] (slice) |
| Multi-line | """ not supported (use +) | """triple quotes""" |
| Regex | String.matches(), Pattern | re.match(), re.search() |
| Character access | s.charAt(1) | s[1] |
8. Common Pitfalls
Pitfall 1: Using == for String Comparison
javaScanner sc = new Scanner(System.in); String input = sc.next(); // User types "yes" if (input == "yes") { // ALWAYS false! System.out.println("You said yes"); }
Why:
sc.next() returns a new String object, different from the literal "yes". Fix: if (input.equals("yes")) or if ("yes".equals(input)) (null-safe).Pitfall 2: String Concatenation in Loops
javaString result = ""; for (int i = 0; i < 1000; i++) { result += i; // O(n²) time, memory hog }
Why: Each
+= creates a new String, copying all previous content. Fix: Use StringBuilder:javaStringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); } String result = sb.toString();
Pitfall 3: substring Index Confusion
javaString s = "Hello"; System.out.println(s.substring(1, 4)); // "ell" (NOT indices 1-4!)
Why:
substring(1, 4) goes from index 1 inclusive to index 4 exclusive. Fix: Remember: substring(beginIndex, endIndex) — endIndex is exclusive.Pitfall 4: Forgetting That Strings Are Immutable
javaString s = "Hello"; s.toUpperCase(); // Returns NEW string, doesn't modify s! System.out.println(s); // "Hello" — unchanged!
Why: All String manipulation methods return a new string; the original is unchanged. Fix:
s = s.toUpperCase(); (assign result back).Pitfall 5: Using + Inside StringBuilder
javaStringBuilder sb = new StringBuilder(); sb.append("Hello " + name); // Still creates intermediate String!
Why: The
+ concatenation happens BEFORE append(), defeating the purpose. Fix: sb.append("Hello ").append(name);9. Practice Questions
Q1: What is the output?javaString s1 = "Java"; String s2 = "Java"; String s3 = new String("Java"); System.out.println(s1 == s2); System.out.println(s1 == s3); System.out.println(s1.equals(s3));Answer:pseudotrue false trues1 == s2: Both are literals, same pool reference → true.s1 == s3: s3 is a new String object → false.s1.equals(s3): Content is identical → true. Q2: Why is this code inefficient? Rewrite it.javaString csv = ""; for (String s : items) { csv = csv + s + ","; }Answer: Each iteration creates 2-3 new String objects (the+creates intermediate strings). For largeitems, this is O(n²).Fix with StringBuilder:javaStringBuilder sb = new StringBuilder(); for (String s : items) { sb.append(s).append(","); } String csv = sb.toString(); // Or use: String csv = String.join(",", items);Q3: What is the output?javaString s = "Hello"; s.concat(" World"); System.out.println(s);Answer:Helloconcat()returns a new string but doesn't modify the original. Since the result isn't assigned, it's discarded.sstill references"Hello". The correct way:s = s.concat(" World");Q4: What does this code return?javaString s = "banana"; System.out.println(s.indexOf('a')); System.out.println(s.lastIndexOf('a')); System.out.println(s.indexOf("na")); System.out.println(s.indexOf('x'));Answer:pseudo1 5 2 -1
indexOf('a'): first 'a' is at index 1lastIndexOf('a'): last 'a' is at index 5indexOf("na"): "na" starts at index 2indexOf('x'): not found → -1 Q5: What is the difference between StringBuilder and StringBuffer?Answer:StringBufferis synchronized (thread-safe) whileStringBuilderis not. In single-threaded code,StringBuilderis faster (no synchronization overhead). UseStringBuilderunless you need thread safety, which is rare for string manipulation.Both have the same API (append, insert, delete, reverse, etc.) and both are mutable. Q6: What is the output?javaString s = "Hello World"; System.out.println(s.substring(6)); System.out.println(s.substring(0, 5));Answer:pseudoWorld Hello
substring(6): from index 6 to end → "World"substring(0, 5): from index 0 inclusive to 5 exclusive → "Hello"Remember:substring(beginIndex)orsubstring(beginIndex, endIndex)where endIndex is exclusive. Q7: How does the intern() method work?Answer:intern()checks the string pool: if a matching string exists in the pool, it returns the pool reference. Otherwise, it adds the string to the pool and returns its reference.javaString s1 = new String("Hello"); String s2 = s1.intern(); // Returns pool reference String s3 = "Hello"; System.out.println(s2 == s3); // true — both are pool references System.out.println(s1 == s3); // false — s1 is heap, s3 is poolQ8: What is the output?javaSystem.out.println("Hello".compareTo("Hello")); System.out.println("A".compareTo("B")); System.out.println("B".compareTo("A")); System.out.println("hello".compareTo("Hello"));Answer:pseudo0 -1 1 32
- Equal strings return 0
- "A" < "B" → negative (-1)
- "B" > "A" → positive (1)
- "hello" > "Hello" because lowercase letters have higher Unicode values Q9: Write code to reverse a string in Java.
Answer:java// Method 1: StringBuilder String original = "Hello"; String reversed = new StringBuilder(original).reverse().toString(); System.out.println(reversed); // "olleH" // Method 2: Manual loop public static String reverse(String s) { char[] chars = s.toCharArray(); int left = 0, right = chars.length - 1; while (left < right) { char temp = chars[left]; chars[left] = chars[right]; chars[right] = temp; left++; right--; } return new String(chars); }Q10: What is the output of this code and why?javaString path = "C:\\Users\\John\\file.txt"; System.out.println(path);Answer:C:\Users\John\file.txtIn Java strings,\\is an escape sequence representing a single backslash character\. Each\\in the literal produces one\in the actual string. This is because\is the escape character, so to include a literal\, you double it.
📐 Key Concepts
| Concept | Key Points |
|---|---|
| Immutability | Strings cannot be changed; every modification creates a new string |
| String pool | Literals are stored in a pool for reuse; intern() accesses the pool |
| Content comparison | Always use .equals(), never == |
| StringBuilder | Mutable; use for concatenation in loops |
| Common methods | length(), charAt(), substring(), indexOf(), split(), trim() |
| Conversions | toCharArray(), Integer.parseInt(), String.valueOf() |
| Performance | StringBuilder is 100-1000x faster than + in loops |
🔗 Cross-References
- Next: Classes & Objects
- Related: Generics & Collections — more data structures
- Python Comparison: BSCS1002 — Strings in Python
- Reference: Oracle Java Tutorials — Strings Join Discord Previous2.1 ArraysNext2.3 Classes & Objects