Java Strings: Immutability, the String Pool, equals vs == and String Methods
These are the notes for Java episode 9. You will learn why name.toUpperCase(); on its own changes nothing (Strings are immutable), the string pool and why two literals are the same object, why == rejects the right username while equals and equalsIgnoreCase work, how to read a String with length(), charAt, indexOf and substring, the StringIndexOutOfBoundsException, and how to clean messy input with strip, replace and split. Every example below runs as-is in the SyntaxPilot compiler, with its real output from javac 21.
The problem: a registration desk full of messy names
At a college fest, volunteers type every visitor's name in a hurry: " priya SHARMA ". Extra spaces, mixed case. The badge printer needs clean capitals, and the volunteer login needs to compare two pieces of text. Both jobs have a trap.
Strings are immutable
A String is Java's type for text. It is an object with methods (things it can do), such as toUpperCase(). The obvious attempt:
public class Main {
public static void main(String[] args) {
String name = "priya";
name.toUpperCase();
System.out.println(name);
}
}
priya
Nothing changed, with no error and no warning.
Why: a String can never change after it's created.
toUpperCase()doesn't touch the original; it builds a new String and returns it. We didn't store the new String, so it was thrown away.
Real-life example: a printed certificate.
| At the print shop | In Java |
|---|---|
| once printed, the ink is final | a String never changes: it is immutable |
| a spelling fix means printing a new certificate | every String method returns a new String |
| put the new one in your file, or it's lost | store the result: name = name.toUpperCase(); |
public class Main {
public static void main(String[] args) {
String name = "priya";
String loud = name.toUpperCase();
System.out.println(name + " / " + loud);
name = name.toUpperCase();
System.out.println(name);
}
}
priya / PRIYA
PRIYA
name = name.toUpperCase(); doesn't change the old String either: it makes the variable name point to the new one. The old "priya" is simply forgotten (and later cleaned up by Java).
The login that fails: == on Strings
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String saved = "Rahul";
String typed = sc.next();
if (typed == saved) {
System.out.println("Welcome, " + typed);
} else {
System.out.println("Access denied");
}
}
}
Input Rahul:
Access denied
The exact right name, rejected. To see why, we need to know where Strings live.
The string pool
Real-life example: a school library.
| In a school library | In Java |
|---|---|
| ask for a book by title: everyone shares the one copy on the shelf | identical string literals share one object in the string pool |
| buy your own copy: same words, a different book | new String("Rahul") or text read from the keyboard is a new object |
| "is it the same book?" vs "does it say the same thing?" | == vs equals |
A string literal is text written in double quotes in your code ("Rahul"). Java keeps literals in a special area of memory called the string pool and reuses one object for every identical literal.
public class Main {
public static void main(String[] args) {
String a = "Rahul";
String b = "Rahul";
String c = new String("Rahul");
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a.equals(c));
}
}
true
false
true
variables string pool heap (outside the pool)
a ───────┐
├──► "Rahul"
b ───────┘
c ─────────────────────────────────────► "Rahul" (a separate object)
==compares references: is this the very same object?equalscompares the characters.
That's why the login failed: saved is a pooled literal, typed is a new object built from keyboard input. Same letters, different objects.
Immutability and the pool go together: because many variables can share one pooled String, it must never change. If it could, changing it through one variable would change it for every other variable too.
The fix: equals and equalsIgnoreCase
Replace typed == saved with typed.equals(saved):
| Input | == |
equals |
equalsIgnoreCase |
|---|---|---|---|
Rahul |
Access denied | Welcome, Rahul | Welcome, Rahul |
rahul |
Access denied | Access denied | Welcome, rahul |
equals is strict: r and R are different characters. equalsIgnoreCase compares the letters but ignores capital vs small. Use equals for passwords (case matters) and equalsIgnoreCase for names and usernames.
Tip: when one side is a literal, write it first: "Rahul".equals(typed). It works even if typed is null.
Reading a String: length(), charAt, indexOf, substring
A String is a row of characters numbered from 0, like an array.
P r i y a
0 1 2 3 4 length() = 5
| Method | Returns | "Priya" example |
|---|---|---|
length() |
number of characters (a method: brackets!) | 5 |
charAt(i) |
the char at index i |
charAt(0) → 'P' |
indexOf(x) |
the first index of x, or -1 if not found |
indexOf('y') → 3 |
substring(a, b) |
characters from a up to but not including b |
substring(1, 3) → "ri" |
substring(a) |
from a to the end |
substring(2) → "iya" |
public class Main {
public static void main(String[] args) {
String email = "priya.sharma@gmail.com";
System.out.println(email.length());
System.out.println(email.charAt(0));
int at = email.indexOf('@');
System.out.println(at);
System.out.println(email.substring(0, at));
System.out.println(email.substring(at + 1));
}
}
22
p
12
priya.sharma
gmail.com
Going past the end
String name = "Priya";
System.out.println(name.charAt(5));
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
...
at Main.main(Main.java:4)
The last character is at length() - 1.
Asking questions about a String
public class Main {
public static void main(String[] args) {
String email = "priya.sharma@gmail.com";
System.out.println(email.contains("@"));
System.out.println(email.endsWith(".com"));
System.out.println(email.startsWith("rahul"));
System.out.println(email.indexOf('#'));
}
}
true
true
false
-1
indexOf doesn't crash when it finds nothing: it returns -1, meaning "not found".
Cleaning input: strip, replace, split
public class Main {
public static void main(String[] args) {
String raw = " Priya Sharma ";
System.out.println("[" + raw + "]");
System.out.println("[" + raw.strip() + "]");
System.out.println(raw.strip().replace(" ", " "));
String[] parts = "Priya Sharma".split(" ");
System.out.println(parts[0] + " | " + parts[1] + " | " + parts.length);
}
}
[ Priya Sharma ]
[Priya Sharma]
Priya Sharma
Priya | Sharma | 2
strip()removes spaces (and other whitespace) at both ends, not in the middle.replace(old, new)replaces every occurrence.split(" ")cuts the String at every space and returns aString[].trim()is the older version ofstrip();strip()also understands Unicode spaces, so prefer it (Java 11+).- Calls can be chained:
raw.strip().toLowerCase()callstoLowerCaseon the String thatstripreturned.
Other useful methods: toLowerCase(), isEmpty(), isBlank(), lastIndexOf(x), repeat(n), compareTo(other) (dictionary order: negative, 0 or positive).
Common mistakes (and the real error messages)
| Mistake | Real message | Why | Fix |
|---|---|---|---|
name.toUpperCase(); |
no error, name unchanged |
Strings are immutable | name = name.toUpperCase(); |
if (typed == saved) |
no error, wrong answer | == compares objects |
typed.equals(saved) |
name.charAt(5) on "Priya" |
StringIndexOutOfBoundsException: Index 5 out of bounds for length 5 |
indexes go 0 to length() - 1 |
name.charAt(name.length() - 1) |
name.length |
cannot find symbol symbol: variable length location: variable name of type String |
for a String, length() is a method |
name.length() |
String s = 'Priya'; |
unclosed character literal |
single quotes hold one char |
"Priya" |
"Priya".substring(3, 1) |
StringIndexOutOfBoundsException: Range [3, 1) out of bounds for length 5 |
start must be ≤ end | substring(1, 3) |
Mini project: username maker
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("Full name?");
String full = sc.nextLine().strip().toLowerCase();
String[] parts = full.split(" ");
String first = parts[0];
String last = parts[parts.length - 1];
String user = first.charAt(0) + last;
String nice = first.substring(0, 1).toUpperCase() + first.substring(1);
System.out.println("Hi " + nice + "! Your username: " + user);
System.out.println("Letters in name: " + full.replace(" ", "").length());
}
}
Input priya SHARMA:
Full name?
Hi Priya! Your username: psharma
Letters in name: 11
nextLine() reads the whole line, spaces included (next() stops at the first space). first.charAt(0) + last works because one side is a String, so + joins. Extend it: if the name has a middle name, add its first letter too (priya k sharma → pksharma).
The next problem
Building one long String in a loop (s = s + i; tens of thousands of times) creates a new String every round and copies all the old characters into it: very slow. Episode 10 times it and fixes it with StringBuilder.
Practice
- Count the vowels in a word.
- Check whether a word is a palindrome (
madam). - Print a name's initials:
Priya Sharma→P.S. - Hide an email:
priya@gmail.com→p****@gmail.com. - Count how many times the letter
aappears in a sentence.
Solutions and hints
java String w = "education"; int v = 0; for (int i = 0; i < w.length(); i++) { if ("aeiou".indexOf(w.charAt(i)) != -1) { v++; } } System.out.println(v); // 5java String w = "madam"; boolean pal = true; for (int i = 0; i < w.length() / 2; i++) { if (w.charAt(i) != w.charAt(w.length() - 1 - i)) { pal = false; } } System.out.println(pal); // true(Comparing twochars with==is fine:charis a primitive, not an object.)String[] p = "Priya Sharma".split(" "); System.out.println(p[0].charAt(0) + "." + p[1].charAt(0) + ".");→P.S.java String e = "priya@gmail.com"; int at = e.indexOf('@'); System.out.println(e.charAt(0) + "*".repeat(at - 1) + e.substring(at)); // p****@gmail.com- Loop with
charAtand count== 'a', ors.length() - s.replace("a", "").length().
Quick quiz
What does this print?
String s = "java";
s.toUpperCase();
System.out.println(s.substring(1, 3) + s.length());
- A.
AV4 - B.
av4 - C.
ava4 - D.
jav4
Answer
B. av4. s.toUpperCase() returns a new String that nobody stores, so s is still "java". substring(1, 3) is index 1 and 2: "av". length() is 4, and String + int joins: av4.
Interview questions
- Why are Strings immutable in Java? So pooled literals can be safely shared, for security (file names, URLs, class names can't be changed after checks), thread safety, and so the hash code can be cached (useful in
HashMap). - What is the string pool? An area of the heap where Java keeps one shared object per distinct string literal.
- Difference between
==andequalsfor Strings?==compares references;equalscompares characters. - How many objects does
new String("Rahul")create? Up to two: the literal"Rahul"in the pool (if not already there) and a new object on the heap. - What does
intern()do? Returns the pooled copy of a String, adding it to the pool if needed:new String("Rahul").intern() == "Rahul"istrue. trim()vsstrip()? Both remove leading and trailing whitespace;strip()(Java 11) also handles Unicode whitespace.- What does
indexOfreturn when nothing is found?-1.
Glossary
| Term | Meaning | Example |
|---|---|---|
| String | Java's type for text; an object | "Priya" |
| object | a value that has data and methods | a String |
| method | something an object can do, called with a dot and brackets | name.toUpperCase() |
| immutable | can never change after it's created | every String |
| string literal | text written in double quotes in the code | "Rahul" |
| string pool | the memory area where identical literals share one object | "Rahul" == "Rahul" → true |
| reference | a variable's link to an object; == compares these |
a == c → false |
equals |
compares two Strings' characters | a.equals(c) → true |
equalsIgnoreCase |
compares characters, ignoring capital vs small | "rahul" vs "Rahul" → true |
char |
one character, in single quotes | 'P' |
| index | a character's position, from 0 | charAt(0) |
| substring | a piece cut out of a String | substring(1, 3) → "ri" |
| whitespace | spaces, tabs and new lines | " Priya " |
strip / trim |
remove whitespace at both ends | " a ".strip() → "a" |
split |
cut a String into a String[] at a separator |
"a b".split(" ") |
| chaining | calling a method on the result of another | s.strip().toLowerCase() |
| StringIndexOutOfBoundsException | the runtime crash for an index outside 0..length()-1 | "Priya".charAt(5) |
nextLine() |
Scanner method that reads a whole line, spaces included | sc.nextLine() |
Summary
- Strings are immutable: every method returns a new String, so store the result.
- Identical literals share one object in the string pool; keyboard text and
new Stringare new objects. ==checks for the same object;equalschecks for the same text;equalsIgnoreCaseignores case.length(),charAt,indexOf(-1 = not found),substring(a, b)(end excluded) read a String.strip,replace,split,contains,startsWith,endsWithclean and check input.
Next episode: StringBuilder and StringBuffer: text you can change, and a live race against String.