MA-JAVA0 · Module 5
Strings: methods and StringBuilder
Topics: length · charAt · substring · indexOf · StringBuilder
Overview
Session 5 — Strings: methods and StringBuilder
Strings surround everything you type into a program: usernames, file names, command lines, lines read from a file. This session gives you the tools to inspect and rebuild text — asking for a character at a position, cutting out a substring, finding where a word appears, and comparing text properly.
You will also meet StringBuilder, the class Java offers when you need to assemble a large string piece by piece without paying the cost of creating a fresh String on every step.
Time: about 85 minutes.
Concepts
1. A String is an immutable sequence of characters
You already know String is a class, not a primitive. The detail that drives almost every rule in this session: a String object is immutable — once created, its character sequence never changes. Every method that looks like it "changes" a string actually returns a brand-new string and leaves the original untouched.
String name = "Ava";
String upper = name.toUpperCase(); // "AVA" — new object
System.out.println(name); // still "Ava"
System.out.println(upper); // "AVA"
Why does immutability matter? Because strings are shared freely (as method arguments, as map keys, inside collections) with the safety that nobody can silently mutate text another part of the program relies on. The trade-off is that building new strings repeatedly costs time and memory — that is precisely the problem StringBuilder exists to solve, later in this session.
2. Looking inside: length() and charAt(i)
Two methods unlock everything else:
s.length()— how many characters the string holds. Note the parentheses: array length is the field.length, butStringlength is a method.s.charAt(i)— the character at indexi. Like arrays, indices start at 0, and the last character sits ats.length() - 1. Stepping out of range throwsStringIndexOutOfBoundsException.
String word = "Java";
System.out.println(word.length()); // 4
System.out.println(word.charAt(0)); // 'J'
System.out.println(word.charAt(3)); // 'a'
A common beginner error is to write s.charAt(s.length()) expecting the last character. That index does not exist — the last one is s.length() - 1. Read the expression aloud: "character at position length" is one past the end.
To visit every character, pair charAt with a loop:
String label = "zip";
for (int i = 0; i < label.length(); i++) {
System.out.println(i + " -> " + label.charAt(i));
}
// 0 -> z
// 1 -> i
// 2 -> p
3. Cutting text: substring
substring(start) returns the text beginning at start and running to the end. substring(start, end) returns the text from start up to but not including end — a half-open range, exactly like array slice conventions you will meet again.
String email = "mira@tutor.app";
System.out.println(email.substring(5)); // "@tutor.app"
System.out.println(email.substring(0, 4)); // "mira" (chars 0..3)
System.out.println(email.substring(4, 5)); // "@"
The half-open rule trips up everyone at least once. substring(a, b) includes index a, excludes index b, so the length of the result is always b - a. That makes the math checkable: "mira" is positions 0–3, and 4 - 0 = 4 characters.
A very common idiom is "cut off the last character":
String raw = "count=42";
String withoutSuffix = raw.substring(0, raw.length() - 1); // "count=4"
4. Finding things: indexOf
indexOf answers "where does this character or substring first appear?" It returns the index of the first match, or -1 when there is no match. Checking for -1 is a routine pattern, so do not skip that case.
String sentence = "the quick brown fox";
System.out.println(sentence.indexOf('q')); // 4
System.out.println(sentence.indexOf("brown")); // 10
System.out.println(sentence.indexOf('x')); // 19
System.out.println(sentence.indexOf("zebra")); // -1
There is also a two-argument form that starts searching from a given offset, which lets you walk through every occurrence:
String log = "ok fail ok ok fail";
int from = 0;
int found = log.indexOf("ok", from);
while (found != -1) {
System.out.println("found at " + found);
from = found + 1;
found = log.indexOf("ok", from);
}
5. Comparing text correctly: equals and friends
Never compare strings with ==. == asks "are these the same object?" Two separately created strings with identical text are usually different objects, so == returns false even when the text matches.
String a = "star";
String b = new String("star");
System.out.println(a == b); // false — different objects
System.out.println(a.equals(b)); // true — same text
The safe family of comparisons:
a.equals(b)— exact text match, case-sensitive.a.equalsIgnoreCase(b)— match ignoring letter case, handy for yes/no answers.a.compareTo(b)— returns a negative number ifasorts beforeb, positive if after,0if equal. Useful for ordering, not just equality.Objects.equals(a, b)— the null-safe version you use when either side might benull.
String answer = "YES";
System.out.println(answer.equals("yes")); // false
System.out.println(answer.equalsIgnoreCase("yes")); // true
6. Case, whitespace, and emptiness
Three everyday helpers round out the toolkit:
toUpperCase()/toLowerCase()— new strings in the other case.trim()— new string with leading and trailing whitespace removed. Users type" 42 "far more often than"42".isEmpty()—truewhen the string has zero characters. Check for emptiness after trimming when the input is user-typed.
String input = " e m a i l ";
String cleaned = input.trim().toLowerCase();
System.out.println("[" + cleaned + "]"); // "[e m a i l]"
System.out.println(cleaned.isEmpty()); // false
Notice the chain: input.trim().toLowerCase() works because trim() returns a String, and you can call a method directly on that returned value. Method chaining on immutable strings is safe precisely because each step returns a fresh object.
7. Why StringBuilder?
Building a string with + inside a loop creates a new string on every iteration. For a short loop that is fine; for thousands of steps the runtime keeps allocating and copying, and the pattern slows down.
String result = "";
for (int i = 0; i < 1000; i++) {
result += "x"; // 1000 intermediate strings, 1000 copies
}
StringBuilder is a mutable character buffer. You keep appending to the same object, then call toString() once at the end to freeze the result:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("x"); // mutates one buffer
}
String result = sb.toString();
Key methods:
append(x)— add text, a number, a character, or any object'stoString.insert(pos, x)— put text at an index.deleteCharAt(i)/delete(start, end)— remove characters.reverse()— flip the sequence in place.length()andcharAt(i)— same idea as onString.toString()— the final immutableString.
StringBuilder sb = new StringBuilder("ab");
sb.append('c'); // "abc"
sb.insert(1, "-"); // "a-bc"
sb.reverse(); // "cb-a"
System.out.println(sb.toString()); // "cb-a"
A classic use: build a comma-separated line while keeping the commas between items, not after the last one.
StringBuilder out = new StringBuilder();
for (int v : new int[]{3, 1, 4}) {
if (out.length() > 0) {
out.append(',');
}
out.append(v);
}
System.out.println(out); // "3,1,4"
8. Pitfalls worth memorising
charAt(s.length())— index out of range; the last valid index iss.length() - 1.==for text equality — compare content with.equals, not object identity.substringend index is exclusive —substring(0, 5)returns characters 0–4.- Forgetting
indexOfreturns-1— always check before trusting an index. +inside a loop — preferStringBuilderwhen the number of steps is large or unknown.- Immutable means "returns a new string" —
s.toLowerCase()without assignment does nothing visible.
Examples
Example A — count the vowels
Walk every character and tally the vowels with charAt and a loop.
public class VowelCount {
public static void main(String[] args) {
String text = "java is fun";
int vowels = 0;
for (int i = 0; i < text.length(); i++) {
char c = Character.toLowerCase(text.charAt(i));
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
vowels++;
}
}
System.out.println("vowels: " + vowels); // 4
}
}
Character.toLowerCase normalises case so the check stays simple. Trace the four vowels: a, a, i, u — the 's', 'v', 'f', 'n' consonants are skipped.
Example B — middle of a word
substring makes "give me the middle characters" a one-liner.
public class Middle {
public static void main(String[] args) {
String word = "kayak";
int n = word.length();
String middle = word.substring(n / 2, (n + 1) / 2);
System.out.println("middle: " + middle); // "a"
}
}
For an odd-length word both halves round to the same index. For "kayak" (length 5), n/2 is 2 and (n+1)/2 is 3, so the substring is exactly word.charAt(2) = 'a'. Half-open means the end index 3 is excluded.
Example C — find every occurrence of a word
indexOf with a start offset lets you count how many times "ok" appears in a log line.
public class CountWord {
public static void main(String[] args) {
String log = "ok fail ok ok fail";
int count = 0;
int from = 0;
int at = log.indexOf("ok", from);
while (at != -1) {
count++;
from = at + 1;
at = log.indexOf("ok", from);
}
System.out.println("occurrences: " + count); // 3
}
}
Each iteration advances from past the last find so the search never re-finds the same spot. When indexOf returns -1, the loop stops.
Example D — trim + compare a yes/no answer
User input arrives with stray spaces and unpredictable case. Normalise before comparing.
import java.util.Scanner;
public class Confirm {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("Delete the file? (yes/no)");
String answer = in.nextLine().trim().toLowerCase();
if (answer.equals("yes")) {
System.out.println("Deleting...");
} else {
System.out.println("Cancelled.");
}
}
}
" YES " becomes "yes" after trim().toLowerCase(), so the check needs only one plain comparison.
Example E — build a comma-separated line with StringBuilder
Instead of re-allocating on every +=, append into one mutable buffer and freeze it at the end.
public class Join {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int v : new int[]{3, 1, 4, 1}) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(v);
}
System.out.println(sb); // 3,1,4,1
}
}
The if (sb.length() > 0) guard places commas between items only — no trailing comma, no leading comma.
Example F — reverse a word with StringBuilder.reverse
public class Reverse {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("stressed");
sb.reverse();
System.out.println(sb); // desserts
}
}
One method call flips the whole buffer in place — the kind of operation that would otherwise need an index loop and a second string.
What to look for when reading these
- Index boundaries: every
charAtandsubstringcall stays within0 .. length() - 1. - Immutability habits: case methods are assigned to a variable (
String upper = name.toUpperCase();) rather than discarded. - Normalise before comparing:
trim().toLowerCase()appears wherever user input meets a check.
Bridge
Loops, arrays ↔ text processing
Strings share their backbone with the arrays you just learned:
-
Indices start at zero.
charAt(i)works exactly likearr[i]— the first character is at0, the last atlength() - 1. -
length()parallelsarr.length. The only difference is the parentheses: arrays expose a field,Stringexposes a method. Forget either and the compiler will complain — which is the point. -
Looping over characters is looping over indices. Every
forloop you practised on arrays transfers directly to acharAtloop, and every "sum over the array" becomes a "tally of characters". -
Parsing comes later. This session's
substring/indexOfhabits are the raw material for Session 14'ssplitandparseIntrecipes — once you can cut text reliably, readingSKU|QTY|NOTElines is one more step, not a new world. -
StringBuilderis the mutable twin. Where arrays andStringare fixed-size/immutable,StringBuilderlets you grow a buffer in place — the same "allocate once, fill, then freeze" philosophy you will reuse when you meetArrayListin Session 10.
When charAt, substring, and indexOf feel like old friends, you are ready for classes.
Practice check — strings and StringBuilder
Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.
1.What does `"Java".length()` return?
2.`"substring"` has 10 characters. Which `charAt` call throws `StringIndexOutOfBoundsException`?
3.What does `"banana".indexOf("na")` return?
4.`String a = "data"; String b = new String("data");` — what does `a.equals(b)` return?
5.What does `" hello ".trim().length()` return?
6.Why prefer `StringBuilder` over `+` when building a string inside a long loop?