Matheion

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, but String length is a method.
  • s.charAt(i) — the character at index i. Like arrays, indices start at 0, and the last character sits at s.length() - 1. Stepping out of range throws StringIndexOutOfBoundsException.
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 if a sorts before b, positive if after, 0 if equal. Useful for ordering, not just equality.
  • Objects.equals(a, b) — the null-safe version you use when either side might be null.
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() — true when 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's toString.
  • insert(pos, x) — put text at an index.
  • deleteCharAt(i) / delete(start, end) — remove characters.
  • reverse() — flip the sequence in place.
  • length() and charAt(i) — same idea as on String.
  • toString() — the final immutable String.
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 is s.length() - 1.
  • == for text equality — compare content with .equals, not object identity.
  • substring end index is exclusive — substring(0, 5) returns characters 0–4.
  • Forgetting indexOf returns -1 — always check before trusting an index.
  • + inside a loop — prefer StringBuilder when 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 charAt and substring call stays within 0 .. 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:

  1. Indices start at zero. charAt(i) works exactly like arr[i] — the first character is at 0, the last at length() - 1.

  2. length() parallels arr.length. The only difference is the parentheses: arrays expose a field, String exposes a method. Forget either and the compiler will complain — which is the point.

  3. Looping over characters is looping over indices. Every for loop you practised on arrays transfers directly to a charAt loop, and every "sum over the array" becomes a "tally of characters".

  4. Parsing comes later. This session's substring/indexOf habits are the raw material for Session 14's split and parseInt recipes — once you can cut text reliably, reading SKU|QTY|NOTE lines is one more step, not a new world.

  5. StringBuilder is the mutable twin. Where arrays and String are fixed-size/immutable, StringBuilder lets you grow a buffer in place — the same "allocate once, fill, then freeze" philosophy you will reuse when you meet ArrayList in 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. 1.What does `"Java".length()` return?

  2. 2.`"substring"` has 10 characters. Which `charAt` call throws `StringIndexOutOfBoundsException`?

  3. 3.What does `"banana".indexOf("na")` return?

  4. 4.`String a = "data"; String b = new String("data");` — what does `a.equals(b)` return?

  5. 5.What does `" hello ".trim().length()` return?

  6. 6.Why prefer `StringBuilder` over `+` when building a string inside a long loop?