Immutability
Why Strings Are Immutable
Java Strings are immutable by design, meaning once a String object is created, its value cannot be changed.
What Immutability Means
String str = "Hello";
// This does NOT modify str
String upper = str.toUpperCase(); // "HELLO"
// str is still "Hello"
// A new String object is created for the result
System.out.println(str); // Hello
System.out.println(upper); // HELLO
Reasons for Immutability
// 1. Security
// Strings are used for:
// - Class loading (Class.forName("com.example.MyClass"))
// - Network connections (URL)
// - File paths
// - Database connection strings
// If mutable, these could be changed maliciously
// 2. Thread Safety
// Immutable objects are inherently thread-safe
// Multiple threads can read without synchronization
String shared = "constant"; // Safe to share
// 3. Hash Code Caching
// Can be computed once and cached
String str = "Hello";
int hash1 = str.hashCode(); // Computed
int hash2 = str.hashCode(); // Returns cached value
// 4. String Pool Enablement
// Can safely share identical strings
String a = "Hello";
String b = "Hello";
// Both point to same object in pool
Memory Model
// String literal
String a = "Hello";
// Created in string pool, shared
// new String()
String b = new String("Hello");
// Created in heap, separate object
// intern()
String c = b.intern();
// Added to pool, returns pool reference
Immutability vs const
// Java doesn't have 'const' keyword like C++
// Use 'final' for variables, not values
final String name = "Alice";
// name = "Bob"; // COMPILE ERROR
// But String methods return new objects
String str = "Hello";
str.toUpperCase(); // Returns new String, str unchanged
// Must reassign: str = str.toUpperCase();
String Pool
String Pool Mechanism
The string pool is a special memory area where string literals are stored to save memory.
How Pool Works
// Literal creation
String a = "Hello"; // Created in pool
String b = "Hello"; // Reuses pool object
System.out.println(a == b); // true (same object)
System.out.println(a.equals(b)); // true (same content)
// new String() bypasses pool
String c = new String("Hello"); // Created in heap
System.out.println(a == c); // false (different objects)
System.out.println(a.equals(c)); // true (same content)
intern() Method
// Manually add to pool
String a = new String("Hello"); // Not in pool
String b = a.intern(); // Added to pool
String c = "Hello"; // Already in pool
System.out.println(b == c); // true
// Useful when:
// 1. Reading strings from external sources
// 2. Many duplicate strings
// 3. Memory optimization
Pool Behavior by Java Version
// Java 6: PermGen space (fixed size, limited)
// Can cause: java.lang.OutOfMemoryError: PermGen space
// Java 7+: Heap memory (garbage collected)
// More flexible, no PermGen issues
// Modern JVMs: G1GC with string deduplication
// Can deduplicate strings even with different references
Pool Pitfalls
// Pitfall 1: String concatenation in pool
String a = "Hello";
String b = " World";
String c = a + b; // NOT in pool!
// c is created in heap
// Pitfall 2: new String() with pool string
String d = new String("Hello");
// d is not in pool, even though "Hello" is
// Pitfall 3: intern() in loops
for (int i = 0; i < 1000000; i++) {
String s = new String("hello").intern();
// This adds to pool, but pool has limited size
}
Best Practices
// 1. Use literals when possible
String good = "Hello"; // In pool
String bad = new String("Hello"); // Not in pool
// 2. Use equals() for comparison
if (str.equals("constant")) { }
// 3. Avoid intern() unless necessary
// It's a micro-optimization in most cases
// 4. Be careful with string concatenation
// Use StringBuilder for multiple concatenations
Interning
String Interning
Interning is the process of adding a string to the pool and returning its reference.
How intern() Works
String a = new String("Hello");
String b = a.intern();
// a: Not in pool (heap object)
// b: In pool (pool reference)
String c = "Hello"; // In pool
System.out.println(b == c); // true
System.out.println(a == c); // false
When to Use intern()
// 1. Reading strings from files/database
String dbString = readFromDatabase();
String interned = dbString.intern();
// 2. Many duplicate strings
// Without intern(): 1000 objects for 1000 "hello" strings
// With intern(): 1 object for 1000 "hello" strings
// 3. String comparison optimization
// If many comparisons, intern first
String[] largeArray = readLargeFile();
for (String s : largeArray) {
if (s.intern() == "target") {
// Found!
}
}
Performance Considerations
// intern() has O(1) lookup but:
// - Pool has limited size
// - Can cause GC pressure
// - Not always faster than equals()
// Benchmark example:
String a = new String("Hello");
String b = "Hello";
// Method 1: equals()
for (int i = 0; i < 1000000; i++) {
a.equals(b);
}
// Method 2: intern() + ==
String aInterned = a.intern();
for (int i = 0; i < 1000000; i++) {
aInterned == b;
}
// Method 2 may be faster, but uses pool memory
Alternative: String Deduplication
// Java 9+ with G1GC
// Can deduplicate strings automatically
// Enable with: -XX:+UseG1GC -XX:+UseStringDeduplication
// This deduplicates:
// - Strings created by substring()
// - Strings from external sources
// - Any identical strings in heap
// No need to call intern()
Common Mistakes
// Mistake 1: intern() in tight loop
for (int i = 0; i < 1000000; i++) {
String s = new String("hello").intern();
// Adds to pool repeatedly, wastes memory
}
// Mistake 2: Assuming all strings are interned
String a = new String("Hello");
String b = "Hello";
if (a == b) { } // FALSE!
// Mistake 3: Using intern() for equality
if (str.intern() == "constant") { } // Works, but not recommended
if (str.equals("constant")) { } // Better
Performance
Performance Implications
String Concatenation
// BAD: O(n^2) time complexity
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // Creates new String each iteration
}
// Creates 10000 String objects
// GOOD: O(n) time complexity
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
// Creates 1 StringBuilder object
When to Use StringBuilder
// Use StringBuilder when:
// 1. Multiple concatenations in loop
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s).append(",");
}
// 2. Building strings incrementally
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
// 3. String reversal
String reversed = new StringBuilder(str).reverse().toString();
// Use String when:
// 1. Simple concatenation
String s = "Hello" + " " + "World"; // Compiler optimizes
// 2. Few concatenations
String s = firstName + " " + lastName;
String Comparison Performance
// == is O(1) - reference comparison
// equals() is O(n) - character comparison
// If you intern strings:
String a = str.intern();
String b = target.intern();
if (a == b) { } // O(1) comparison
// But intern() itself is O(n)
// So only beneficial if comparing same strings many times
Memory Usage
// String object overhead:
// - 4 bytes: hash code
// - 8 bytes: reference to char array
// - 16 bytes: object header
// - char array: 2 bytes per character
// Example: "Hello" (5 chars)
// Total: ~30 bytes per String object
// With 10000 "Hello" strings:
// Without pooling: 10000 * 30 = 300KB
// With pooling: 1 * 30 = 30 bytes
String vs StringBuilder
| Scenario | String | StringBuilder |
|---|---|---|
| Single concatenation | ✓ | |
| Loop concatenation | ✓ | |
| Thread safety | ✓ (immutable) | |
| Mutable | ✓ | |
| Performance (loops) | O(n²) | O(n) |
Best Practices
// 1. Use String for simple cases
String name = firstName + " " + lastName;
// 2. Use StringBuilder for loops
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s);
}
// 3. Pre-allocate StringBuilder capacity
StringBuilder sb = new StringBuilder(expectedSize);
// 4. Use String.format() for formatted strings
String msg = String.format("Name: %s, Age: %d", name, age);
Practice Problems
Return the index of the first occurrence of needle in haystack, or -1 if not found.
Example:
Input: haystack = "hello", needle = "ll"
Output: 2
The substring 'll' starts at index 2
Optimal Solution — O(n*m) time, O(1) space
Brute force substring comparison.
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
for (int i = 0; i <= haystack.length() - needle.length(); i++) {
if (haystack.substring(i, i + needle.length()).equals(needle)) {
return i;
}
}
return -1;
}Edge Cases:
- Empty needle
- Needle longer than haystack
- No match
Implement atoi to convert a string to a 32-bit signed integer.
Example:
Input: s = "42"
Output: 42
Convert string to integer
Optimal Solution — O(n) time, O(1) space
Parse character by character, handle edge cases.
public int myAtoi(String s) {
int i = 0, sign = 1, result = 0;
while (i < s.length() && s.charAt(i) == ' ') i++;
if (i < s.length() && (s.charAt(i) == '+' || s.charAt(i) == '-')) {
sign = s.charAt(i++) == '-' ? -1 : 1;
}
while (i < s.length() && s.charAt(i) >= '0' && s.charAt(i) <= '9') {
if (result > Integer.MAX_VALUE / 10 ||
(result == Integer.MAX_VALUE / 10 && s.charAt(i) - '0' > 7)) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
result = result * 10 + (s.charAt(i++) - '0');
}
return result * sign;
}Edge Cases:
- Leading zeros
- Overflow
- No digits
Quiz
1. What does it mean for a String to be immutable?
2. What is the purpose of String.intern()?
3. Why does string concatenation in loops create many objects?
4. What is the primary purpose of String Immutability?
Flashcards
Question
Why are Java Strings immutable?
Click to reveal answer
Answer
For security (class loading, network), thread safety, hash code caching, and string pool support.
Question
What is the string pool?
Click to reveal answer
Answer
A special memory area where string literals are stored to save memory by reusing identical strings.
Question
When should you use StringBuilder instead of String?
Click to reveal answer
Answer
When doing multiple string concatenations in loops. StringBuilder is mutable and O(n) vs String O(n²).
Question
What is String Immutability?
Click to reveal answer
Answer
String Immutability is a key concept in Java programming.
Question
When to use String Immutability?
Click to reveal answer
Answer
Use String Immutability when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.Strings are immutable for security, thread safety, and performance
- 2.String literals are stored in string pool and reused
- 3.Use equals() for content comparison, never ==
- 4.Use StringBuilder for multiple concatenations in loops
Interview Tips
- •Explain why immutability is important for security
- •Know the difference between == and equals()
- •Understand when to use StringBuilder vs String
- •Remember string pool behavior with literals vs new String()
Cheat Sheet
Cheat Sheet
- Immutable: String value cannot change after creation
- Pool: String literals stored in string pool
- intern(): Manually add string to pool
- ==: Reference comparison (use for pool strings)
- equals(): Content comparison (always use for strings)
- StringBuilder: Use for multiple concatenations