String Handling
String is immutable — when you do s = s + "x", a new String object is created; the old one stays as it was.
If you need lots of changes (like in a loop), use StringBuilder instead — it modifies the same object, and is fast.
String s = "Hi";
s = s + " Java"; // new object created
StringBuilder sb = new StringBuilder("Hi");
sb.append(" Java"); // same object modified- String is immutable
- A change creates a new object
- StringBuilder is mutable & fast
When you write String s = "Hi"; (a literal), Java first checks the "String Pool" (a special memory area, inside the Heap) to see if "Hi" already exists — if it does, it just gives you that same reference (no new object is created, memory is saved). This is only possible because String is immutable — if one reference could change the String, every shared reference would get corrupted.
But new String("Hi") *always* creates a new object on the Heap, outside the Pool — even if "Hi" already exists in the pool. That's why comparing Strings created with new String() using == is always risky.
You can manually put any String into the Pool (or get its pool reference if it's already there) by calling intern() — rarely used, but can be useful in memory-sensitive applications.
String a = "Hi";
String b = "Hi";
String c = new String("Hi");
System.out.println(a == b); // true (Pool se same object)
System.out.println(a == c); // false (c alag object hai)
System.out.println(a.equals(c)); // true (content same hai)length() gives the length of the String. charAt(i) gives the character at a specific position. substring(start, end) extracts a portion (the end index is excluded). indexOf("x") finds where "x" first appears (-1 if not found).
contains("x") checks if a substring exists. replace("a","b") replaces every occurrence. trim() (or the modern strip()) removes leading/trailing spaces. split(",") gives an array, split up by the delimiter.
toUpperCase()/toLowerCase() change case. equalsIgnoreCase() is a case-insensitive comparison. All methods return a NEW String (because of immutability) — the original is never changed.
| Method | What It Does | Example |
|---|---|---|
| length() | Length | "Java".length() → 4 |
| charAt(i) | i-th character | "Java".charAt(1) → 'a' |
| substring(a,b) | Extract a portion | "Java".substring(1,3) → "av" |
| indexOf(x) | Find a position | "Java".indexOf('v') → 2 |
| trim() | Remove spaces | " Hi ".trim() → "Hi" |
| split(x) | Split into an array | "a,b".split(",") → ["a","b"] |
| replace(a,b) | Replace | "Hi".replace('H','B') → "Bi" |
String s = " Hello Java ";
System.out.println(s.trim()); // "Hello Java"
System.out.println(s.trim().split(" ")[1]); // "Java"Both are mutable (the same object gets modified, unlike String which creates a new object every time) — they give you methods like append(), insert(), delete(), reverse() that modify the object in place.
Difference: StringBuffer is thread-safe (its methods are synchronized, multiple threads can use it safely at once) but that makes it a bit slow (synchronization overhead). StringBuilder is NOT thread-safe but is fast — for single-threaded code (which is more common), StringBuilder is the recommended choice.
If you need a lot of String concatenation in a loop, use StringBuilder — doing s = s + "x" inside a loop creates a new String object every single time (O(n²) time complexity over the whole loop), whereas StringBuilder.append() modifies the same object (O(n) total).
| String | StringBuilder | StringBuffer | |
|---|---|---|---|
| Mutable? | No | Yes | Yes |
| Thread-safe? | Yes (because immutable) | No | Yes |
| Speed | Slow (for concatenation) | Fast | Medium |
| When to use | Fixed text, need thread-safety | Single-thread, building in loops | Multi-thread String building |
String.format("Name: %s, Age: %d", name, age) lets you build clean strings with placeholders — %s for a String, %d for an integer, %f for a decimal (%.2f for two decimal places). System.out.printf() uses the same syntax to print directly (like format(), but doesn't return anything, just prints straight away).
String msg = String.format("%s scored %.1f%%", "Aarav", 92.567);
// "Aarav scored 92.6%"
System.out.printf("Total: %d items%n", 5);String was made immutable/final for three reasons: (1) Security — things like file paths, network URLs, and database credentials get passed around as Strings; if they were mutable, any function along the way could secretly change them, opening up security holes.
(2) The String Pool can only work safely because of immutability — if one reference could change the value, every shared reference (pointing to that same pooled object) would get corrupted.
(3) Thread-safety comes for free without extra effort — immutable objects can be safely shared across multiple threads, since no thread can change them. This also makes String perfect as HashMap keys (the hashCode never changes, so it can be cached — String actually caches its own hashCode internally for performance).