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" | Result | Why |
|---|---|---|
city.length() | 7 | T-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") | 1 | The FIRST match only |
city.indexOf("ont") | 3 | Works for longer pieces of text too |
city.indexOf("z") | −1 | Not found, the same convention as a linear search |
city.toUpperCase() | "TORONTO" | Returns a new String; city itself is unchanged |
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!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;
}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.