Skip to content
Study Hall
Lesson 4 of 5Arrays
Unit 6 · Lesson 4 of 5

Strings as Sequences of Characters

A String is an indexed sequence of characters, so the array skills you already have — indexing, traversal, searching — carry straight over to text, with a handful of String methods doing the heavy lifting.

6 min read11 practice questionsInteractiveAsk AI about this →

Key things to remember

  1. 1String characters are indexed from 0 to s.length() - 1; length() is a method with brackets, unlike an array's length field.
  2. 2s.charAt(i) returns a char; s.substring(a, b) returns the characters from index a up to but not including b, so its length is b − a.
  3. 3s.indexOf(x) returns the index of the first occurrence of x, or −1 if it is absent.
  4. 4Strings are immutable: toUpperCase, toLowerCase and substring return new Strings, so store the result.
  5. 5Use equals or equalsIgnoreCase for matching text, and compareTo for order: negative means first, 0 means equal, positive means after.
  6. 6A position outside 0 to length − 1 throws StringIndexOutOfBoundsException.

Most of the data people type into a program is text: names, usernames, postal codes, passwords, search terms, chat messages. A program that checks whether a password contains a digit, pulls the domain out of an email address, or decides whether a word reads the same backwards is not doing anything mysterious. It is treating the text as a row of characters, each in a numbered position, and walking along them — exactly what you already do with arrays. The difference is that a String is an object with its own methods, so instead of square brackets you ask it questions: how long are you, what character is at position 3, where does "@" appear?

Indexing a String

The characters of a String are numbered from 0, just like array slots, so the last character is at index length() - 1. Note the brackets: a String's length is a method call, s.length(), while an array's is a field, arr.length. Each character is a char, a single 16-bit value written in single quotes such as 'T', which is a different type from the one-character String "T". Two methods take positions: charAt(i) returns the single character at index i, and substring(start, end) returns a new String that runs from start up to but NOT including end — the same half-open convention as a for loop's i < n. A handy consequence is that end - start is always the length of the piece you get back. Finally, indexOf works in the other direction: give it a character or a piece of text and it returns the index where that first appears, or −1 if it never does.

Call, with city = "Toronto"ResultWhy
city.length()7T-o-r-o-n-t-o is seven characters, indexes 0 to 6
city.charAt(0)'T'Index 0 is the first character
city.charAt(6)'o'The last index is length − 1
city.substring(1, 4)"oro"Indexes 1, 2 and 3 — the end index 4 is excluded
city.substring(3)"onto"With one argument it runs to the end
city.indexOf("o")1The FIRST match only
city.indexOf("ont")3Works for longer pieces of text too
city.indexOf("z")−1Not found, the same convention as a linear search
city.toUpperCase()"TORONTO"Returns a new String; city itself is unchanged
Every result here was produced by running the calls in Java.
A list of length 6 has indexes 0 to 5. There is no element 6.12[0]7[1]9[2]23[3]4[4]16[5]?[6]marks[0] → 12 len(marks) → 6marks[5] → 16 marks[-1] → 16 (Python only)marks[6] → IndexError: list index out of range
Fig 1Indexes start at 0 and stop at length minus 1 — the same rule for the characters of a String as for the slots of an array.
Strings never change — methods hand back new ones
Java Strings are immutable. city.toUpperCase(); on a line by itself builds "TORONTO" and throws it away, leaving city exactly as it was. To keep the result you must store it: city = city.toUpperCase();. The same is true of toLowerCase, substring and trim. This design is deliberate: because no String can be changed after it is made, Java can safely share one copy of identical text between many variables.

Comparing Strings: equals and compareTo

You already know that == on Strings compares whether two variables refer to the same object, not whether they hold the same text, so equals is the right test for exact matches and equalsIgnoreCase is the right test when capitals should not matter, such as a typed command. For ordering, Java gives you a.compareTo(b). It returns 0 when the Strings are equal, a negative number when a comes first, and a positive number when a comes after b. It works character by character from the left: at the first position where the two differ, it subtracts their character codes. "cat".compareTo("car") reaches t and r, and 116 − 114 gives 2. If one String runs out first, as with "app" and "apple", the shorter one comes first. Only the sign is meaningful, so always test < 0, == 0 or > 0, never a particular value.

System.out.println("Ottawa".equals("ottawa"));            // false
System.out.println("Ottawa".equalsIgnoreCase("ottawa"));  // true

System.out.println("apple".compareTo("banana"));   // -1   a comes before b
System.out.println("cat".compareTo("car"));        // 2    t comes after r
System.out.println("app".compareTo("apple"));      // -2   shorter prefix first
System.out.println("Zebra".compareTo("apple"));    // -7   capital Z sorts before a!
Capital letters have smaller character codes than lowercase ones, so compareTo puts every capitalized word first. Use compareToIgnoreCase, or lowercase both sides, for dictionary order.

Looping over the characters

Every array algorithm you know has a String twin. The traversal header becomes for (int i = 0; i < s.length(); i++) and the element becomes s.charAt(i). Counting vowels is the counting pattern with a test inside. Reversing builds a new String by walking from the last index down to 0, because you cannot rearrange the characters of a String in place. A palindrome check is the two-ended pattern from reversing an array: compare the outside pair, move both markers inwards, and stop the moment a pair disagrees. Comparing characters with == is fine here, because char is a primitive type like int — it is only whole Strings that need equals.

public static int countVowels(String text) {
    int count = 0;
    String lower = text.toLowerCase();
    for (int i = 0; i < lower.length(); i++) {
        char c = lower.charAt(i);
        if ("aeiou".indexOf(c) != -1) {     // is c one of the vowels?
            count++;
        }
    }
    return count;
}

public static String reverse(String word) {
    String result = "";
    for (int i = word.length() - 1; i >= 0; i--) {
        result += word.charAt(i);
    }
    return result;
}

public static boolean isPalindrome(String word) {
    String w = word.toLowerCase();
    int left = 0;
    int right = w.length() - 1;
    while (left < right) {
        if (w.charAt(left) != w.charAt(right)) {
            return false;                   // one mismatch is enough
        }
        left++;
        right--;
    }
    return true;
}
countVowels("Algonquin Park") returns 5, reverse("stressed") returns "desserts", and isPalindrome("Racecar") returns true.
Loading interactive…
Step through a loop one statement at a time and watch the index and the running result change.
StringIndexOutOfBoundsException
Asking for a position that does not exist stops the program with StringIndexOutOfBoundsException. The usual causes are i <= s.length() in a loop condition, s.charAt(s.length()) when the last character was meant, substring with an end index larger than the length, and calling charAt(0) on an empty String. The stack trace names the line, and the message names the bad index, though its exact wording differs between Java versions. Check the length first whenever input could be empty.

Worked examples

Reveal one step at a time and try to beat it to the next line.

Example 1
Given String email = "sam.lee@school.ca"; write code that stores the username (before the @) and the domain (after it), and state both values.
Example 2
Trace countVowels("Algonquin Park") and state the return value.
Example 3
Trace isPalindrome("Racecar"). How many pairs of characters are compared, and what is returned?
Example 4
A program sorts names with compareTo and puts "Zebra" before "apple". Explain why and fix it so the order is alphabetical regardless of case.

Where marks get lost

  • Using s.length (no brackets) on a String or arr.length() on an array — each is a compile error.
  • Expecting substring(1, 4) to include index 4. The end index is excluded, so it returns three characters.
  • Calling name.toUpperCase(); without storing the result, then wondering why name is unchanged.
  • Comparing whole Strings with == instead of equals, which can pass in testing and fail on typed input.
  • Looping with i <= s.length() or calling s.charAt(s.length()), which throws StringIndexOutOfBoundsException.

Flashcards 9

Index of the last character of s
What does s.substring(a, b) return?
What does s.indexOf(x) return when x is absent?
What does immutable mean for a String?
+ 5 more

Practice

1
What does "Mississauga".length() return?
2
What does "Hamilton".substring(2, 5) return? Type the characters without quotes.
3
What does "Kingston".indexOf("ston") return?
4
What does "Mississauga".indexOf("ss") return?
5
String s = "hello"; s.toUpperCase(); System.out.println(s); What is printed?
6
What is the sign of "cat".compareTo("car")?
7
What is printed? String s = "banana"; int n = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == 'a') { n++; } } System.out.println(n);
8
What does reverse("stressed") return, using the reverse method from this lesson? Type the word without quotes.
9
Put these Strings in the order compareTo sorts them, smallest first.
  1. 1"apple"
  2. 2"banana"
  3. 3"apples"
  4. 4"Zebra"
10
With String city = "Toronto"; match each call to its result.
city.charAt(2)
city.substring(3)
city.indexOf("o")
city.indexOf("z")
11
String w = "hello"; System.out.println(w.charAt(w.length())); What happens?

Spot a mistake on this page? Let us know