Java String Handling: Immutability, Concatenation, and Internal Mechanics
String Class Fundamentals
In Java, String represents immutable character sequences, residing in the java.lang package. Once instantiated, its content cannot be altered, requiring new objects for modifications.
Creation Approaches
Literal Initialization
Direct assignment uses the string constant pool for reuse:
String greeting = "Hello"; // References pool entry
String message = "World";
Pool optimization ensures identical literals share one object:
String a = "Java";
String b = "Java";
System.out.println(a == b); // true (same pool reference)
Constructor-Based Instantiation
Explicit new creates heap-allocated objects, bypassing the pool:
String explicitStr = new String("Constructed"); // Heap allocation
String fromChars = new String(new char[]{'A', 'B', 'C'}); // From char array
Each new call generates distinct objects:
String c = new String("Java");
String d = new String("Java");
System.out.println(c == d); // false (different heap instances)
Content Comparison
Reference Equality (==)
Checks memory addresses, not content:
String literal = "test";
String constructed = new String("test");
System.out.println(literal == constructed); // false
Value Equality (equals())
Compares character sequences:
String s1 = new String("data");
String s2 = new String("data");
System.out.println(s1.equals(s2)); // true
Case-Insensitive Comparison
equalsIgnoreCase() ignores case differences:
String upper = "CASE";
String lower = "case";
System.out.println(upper.equalsIgnoreCase(lower)); // true
Core String Methods
Traversal
Iterate via indices or conversion to char arrays:
String text = "Traverse";
// Index-based loop
for (int i = 0; i < text.length(); i++) {
System.out.print(text.charAt(i) + " ");
}
// Char array iteration
for (char ch : text.toCharArray()) {
System.out.print(ch + " ");
}
Character Statistics
Count uppercase, lowercase, and digits:
String input = "AbC123xYz";
int upper = 0, lower = 0, digits = 0;
for (char ch : input.toCharArray()) {
if (Character.isUpperCase(ch)) upper++;
else if (Character.isLowerCase(ch)) lower++;
else if (Character.isDigit(ch)) digits++;
}
System.out.printf("Upper: %d, Lower: %d, Digits: %d", upper, lower, digits);
Manipulation Utilities
- Split: Divide using delimiters
String csv = "apple,banana,cherry"; String[] fruits = csv.split(","); - Substring: Extract poritons (left-inclusive, right-exclusive)
String full = "Programming"; String sub = full.substring(3, 7); // "gram" - Replace: Substitute characters/sequences
String original = "red apple"; String updated = original.replace("red", "green"); // "green apple" - Case Conversion:
String mixed = "MiXeD CaSe"; System.out.println(mixed.toUpperCase()); // "MIXED CASE" System.out.println(mixed.toLowerCase()); // "mixed case"
Key Terminology
- String Constant Pool: JVM-managed memory area storing unique string literals for reuse.
- Heap Memory: Non-pool storage for objects created via
new, allowing duplicate content. - Empty String vs Null:
""is a valid zero-length string object;nullindicates no object reference.
StringBuilder: Mutable String Construction
StringBuilder enables efficient string modification via mutable internal buffers, avoiding intermediate object creation.
Basic Usage
Initialize with default (16-char buffer) or initial content:
StringBuilder builder = new StringBuilder(); // Empty, 16-char capacity
builder.append("Mutable");
builder.append(" String");
System.out.println(builder.toString()); // "Mutable String"
Performance Advantage
Compared to + concatenation (creates new objects per operation), StringBuilder modifies in-place:
// Inefficient: ~100ms for 100k iterations
long start = System.currentTimeMillis();
String concat = "";
for (int i = 0; i < 100_000; i++) {
concat += "item";
}
long end = System.currentTimeMillis();
System.out.println("Concat time: " + (end - start) + "ms");
// Efficient: ~5ms for 100k iterations
start = System.currentTimeMillis();
StringBuilder efficient = new StringBuilder();
for (int i = 0; i < 100_000; i++) {
efficient.append("item");
}
end = System.currentTimeMillis();
System.out.println("StringBuilder time: " + (end - start) + "ms");
Common Methods
append(): Add content (supports chaining)insert(int offset, String s): Insert at positiondelete(int start, int end): Remove rangereverse(): Invert character ordertoString(): Convert to immutableString
Example: Reverse input string
Scanner scanner = new Scanner(System.in);
System.out.print("Enter text: ");
String input = scanner.nextLine();
String reversed = new StringBuilder(input).reverse().toString();
System.out.println("Reversed: " + reversed);
StringJoiner: Delimited Sequence Builder
Introduced in Java 8, StringJoiner simplifies joining elements with delimiters, prefixes, and suffixes.
Basic Joining
List<String> colors = Arrays.asList("Red", "Green", "Blue");
StringJoiner joiner = new StringJoiner(", "); // Comma-space delimiter
colors.forEach(joiner::add);
System.out.println(joiner.toString()); // "Red, Green, Blue"
With Prefix/Suffix
StringJoiner bracketJoiner = new StringJoiner(", ", "[", "]");
bracketJoiner.add("One").add("Two").add("Three");
System.out.println(bracketJoiner); // "[One, Two, Three]"
Merging Joiners
Combine multiple joiners into one:
StringJoiner first = new StringJoiner("-").add("A").add("B");
StringJoiner second = new StringJoiner(":").add("C").add("D");
first.merge(second);
System.out.println(first); // "A-B:C-D"
Internal String Mechanics
Storage Principles
- Literals: Stored in the constant pool; reused across references.
- New Instances: Allocated on the heap, independent of the pool.
Concatenation Under the Hood
- Compile-Time Constants: Adjacent literals are merged during compilation:
String merged = "Hello" + " " + "World"; // Compiles to "Hello World" - Runtime Variables: Involve temporary
StringBuilderinstences (pre-Java 9) or dynamic invocation (Java 9+), creating new objects:String base = "Base"; String combined = base + "Suffix"; // Runtime concatenation → new object
Immutability Rationale
- Security: Prevents unintended modification of shared strings (e.g., class/method names).
- Thread Safety: Immutable objects are inherently thread-safe.
- Optimization: Enables pooling and hashcode caching (via private
final char[]).