SEN1002 · Week 8 · Strings, Characters and Regular Expressions

String, StringBuilder, Character and regular expressions

String methods and immutability, mutable text with StringBuilder, the Character class, and validation, split and replaceAll with regex. A topic full of short output questions.

StringimmutablesubstringindexOfcharAtcompareToStringBuilderappendinsertreverseCharacterregular expressionmatchessplitreplaceAllPatternMatcher

Text processing is in every program: validating a username, splitting a CSV line, counting words. In the exam this topic comes as short but tricky output questions: the end index of substring, the immutability of String, char arithmetic, the empty parts of split. There is also a “write a method” question: palindrome, word counting, converting to upper case.

Golden rules:

  1. String is immutable. Methods return a new String; if you do not assign it, the result is lost.
  2. substring(a, b) does not take b. The length is b - a.
  3. char is a number. 'A' + 2 gives an int (67); if you want a character, cast it.
  4. StringBuilder for text that changes. Its methods change the same object.
  5. A backslash in a regex is written twice in a Java String: "\\d+".

String methods

String s = "Object Oriented";
System.out.println(s.length());
System.out.println(s.charAt(7));
System.out.println(s.indexOf('e'));
System.out.println(s.lastIndexOf('e'));
System.out.println(s.indexOf("Or"));
System.out.println(s.indexOf('z'));
System.out.println(s.substring(7));
System.out.println(s.substring(0, 6));
System.out.println(s.substring(2, 4));
System.out.println(s.toUpperCase() + " " + s.toLowerCase());
System.out.println("[" + "  hi there ".trim() + "]");
System.out.println(s.replace('e', '3'));
System.out.println(s.startsWith("Obj") + " " + s.endsWith("ED") + " " + s.contains("ted"));
15
O
3
13
7
-1
Oriented
Object
je
OBJECT ORIENTED object oriented
[hi there]
Obj3ct Ori3nt3d
true false true
index:      0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
character:  O b j e c t _ O r i e  n  t  e  d      (_ = space)
substring(2, 4) → 2 and 3 → "je"        substring(7) → from 7 to the end → "Oriented"
MethodReturnsNote
length()intIn an array length is a field, in String it is a method
charAt(i)charStringIndexOutOfBoundsException if i >= length()
indexOf(x) / lastIndexOf(x)int-1 if not found
substring(a, b)Stringa included, b excluded
equals, equalsIgnoreCasebooleanContent comparison
compareTointDictionary order, code difference of the first differing character
toUpperCase, toLowerCase, trimStringA new String
replace(a, b)StringPlain text replacement, all occurrences
split(regex)String[]Splits into parts
toCharArray()char[]Array of characters
String.valueOf(x)StringConverts a number to text

PITFALL · bounds. "java".substring(2, 5) and "java".charAt(4) throw StringIndexOutOfBoundsException when they run. But "java".substring(4) does not give an error, it returns an empty String (isEmpty() is true).

Immutability

Once a String object is created, its content does not change. Methods such as toUpperCase, concat and replace produce a new String.

String t = "java";
t.toUpperCase();          // result not assigned, lost
t.concat("!");            // same
System.out.println(t);
t = t.toUpperCase();      // t now points to the new object
System.out.println(t);
java
JAVA

For the same reason, after String u = "A"; String v = u; v += "B"; u is still "A": += creates a new object and assigns it to v. With StringBuilder, however, two references change the same object (below).

Comparison

System.out.println("Java".equals("java") + " " + "Java".equalsIgnoreCase("java"));
System.out.println("Apple".compareTo("apple"));
System.out.println("cat".compareTo("car"));
System.out.println("ab".compareTo("abcd"));
false true
-32
2
-2

The Character class and char arithmetic

A char is really a 16-bit number (a Unicode code). When it enters arithmetic it is promoted to int. Memorize: '0' = 48, 'A' = 65, 'a' = 97; the gap between upper and lower case is 32.

System.out.println('A' + 2);           // int
System.out.println((char) ('A' + 2));  // char
System.out.println("A" + 2);           // String concatenation
char c = 'x';
c++;                                   // ++ stays a char
System.out.println(c);
System.out.println('7' - '0');         // value of the digit
67
C
A2
y
7

The static methods of the Character class:

MethodExampleResult
isLetter(c)isLetter('B')true
isDigit(c)isDigit('7')true
isLetterOrDigit(c)isLetterOrDigit('_')false
isUpperCase(c) / isLowerCase(c)isUpperCase('b')false
isWhitespace(c)isWhitespace(' ')true
toUpperCase(c) / toLowerCase(c)toUpperCase('q')'Q'
getNumericValue(c)getNumericValue('8')8
String mix = "a1B2 c3!";
int letters = 0, digits = 0, upper = 0, spaces = 0;
for (int i = 0; i < mix.length(); i++) {
    char ch = mix.charAt(i);
    if (Character.isLetter(ch)) letters++;
    if (Character.isDigit(ch)) digits++;
    if (Character.isUpperCase(ch)) upper++;
    if (Character.isWhitespace(ch)) spaces++;
}
System.out.println(letters + " " + digits + " " + upper + " " + spaces);
3 3 1 1

StringBuilder

If you build text piece by piece or change it often, use StringBuilder. Its content is changeable (mutable); its methods change the same object and return it, which is why they are chained.

StringBuilder sb = new StringBuilder("code");
sb.append(42).append('!');
System.out.println(sb);
sb.insert(0, "my ");
System.out.println(sb);
sb.reverse();
System.out.println(sb);
sb.reverse().deleteCharAt(sb.length() - 1).setCharAt(0, 'M');
System.out.println(sb + " len=" + sb.length());

StringBuilder sb2 = new StringBuilder("abc");
sb2.replace(1, 2, "XYZ");     // 1 included, 2 excluded
System.out.println(sb2);
sb2.delete(0, 2);
System.out.println(sb2);
code42!
my code42!
!24edoc ym
My code42 len=9
aXYZc
YZc
StringStringBuilder
ImmutableMutable
s = s + x a new object each timesb.append(x) the same object
equals compares contentequals is not overridden: address
No reverseHas reverse()
Created with a literal: "abc"new StringBuilder("abc")
StringBuilder x = new StringBuilder("hi"), y = new StringBuilder("hi");
System.out.println(x.equals(y));
System.out.println(x.toString().equals(y.toString()));

StringBuilder p = new StringBuilder("A");
StringBuilder q = p;
q.append("B");            // same object
System.out.println(p);
false
true
AB

PITFALL · a loop that prepends. r = i + r; and rb.insert(0, i); both put each element at the front. In a loop from 0 to 3, both produce 3210. If you had appended at the end, it would be 0123.

Regular expressions

A regex is a small language that describes a text pattern. In Java you use it in three places: matches (validation), split (splitting), replaceAll (replacing).

PatternMeaningExample match
.any charactera, 7, !
\d / \Ddigit / non-digit5
\wletter, digit or _x, _
\swhitespace character (space, tab, newline)" "
[abc]a, b or cb
[a-z], [A-Z0-9]rangek, Q, 4
[^0-9]not a digitx
X*X zero or more times"", XXX
X+X one or more timesX, XX
X?X may or may not be therecolou?r → color, colour
X{n}, X{n,}, X{n,m}exactly n, at least n, between n and m\d{9}

Because \ is the escape character in a Java String, every backslash of the regex is written twice: \d+ in a regex, "\\d+" in Java. To search for a special character such as the dot literally, you also escape it: "\\.".

matches: validation

matches tells whether the whole text fits the pattern.

System.out.println("202312345".matches("\\d{9}"));          // 9-digit student number
System.out.println("20231234".matches("\\d{9}"));
System.out.println("abc123".matches("[a-z]+\\d+"));
System.out.println("abc123x".matches("[a-z]+\\d+"));
System.out.println("Ankara".matches("[A-Z][a-z]*"));        // a word starting with a capital letter
System.out.println("ali@bau.edu.tr".matches("[\\w.]+@[\\w.]+\\.[a-z]{2,}"));
System.out.println("05321234567".matches("05\\d{9}"));      // mobile phone
true
false
true
false
true
true
true

split and replaceAll

System.out.println(Arrays.toString("a,b,,c,,".split(",")));
System.out.println("a,b,,c,,".split(",").length);
System.out.println(Arrays.toString(",x,y".split(",")));
System.out.println(Arrays.toString("  one   two three ".trim().split("\\s+")));
System.out.println(Arrays.toString("1.5.9".split("\\.")));
System.out.println("1.5.9".split(".").length);   // "." is any character: every character is a separator
[a, b, , c]
4
[, x, y]
[one, two, three]
[1, 5, 9]
0
System.out.println("programming".replaceAll("[aeiou]", "*"));
System.out.println("a1b22c333".replaceAll("\\d+", "#"));
System.out.println("too    many   spaces".replaceAll(" +", " "));
System.out.println("2026-10-28".replaceAll("(\\d+)-(\\d+)-(\\d+)", "$3/$2/$1"));
System.out.println("x.y.z".replace(".", "-") + " " + "x.y.z".replaceAll(".", "-"));
pr*gr*mm*ng
a#b#c#
too many spaces
28/10/2026
x-y-z -----

Parentheses create a group; in the replacement text you can refer back to the groups by the numbers 1, 2, 3 (the date example above).

Pattern and Matcher

To find, one by one, all the parts inside a text that fit the pattern:

Matcher m = Pattern.compile("\\d+").matcher("Room 12, floor 3, gate 104");
int sum = 0;
while (m.find()) {
    System.out.print(m.group() + " ");
    sum += Integer.parseInt(m.group());
}
System.out.println("sum=" + sum);
12 3 104 sum=119

matches wants the whole text, while find searches inside the text for the next part that fits.

A solved writing question: palindrome

“A man, a plan, a canal: Panama”: after throwing away everything except letters and digits and converting to lower case, is it the same as its reverse?

static boolean isPalindrome(String s) {
    StringBuilder clean = new StringBuilder();
    for (char c : s.toCharArray())
        if (Character.isLetterOrDigit(c)) clean.append(Character.toLowerCase(c));
    String fwd = clean.toString();               // copy BEFORE reverse
    return fwd.equals(clean.reverse().toString());
}
System.out.println(isPalindrome("A man, a plan, a canal: Panama"));
System.out.println(isPalindrome("Java"));
true
false

Taking fwd before reverse is a must: reverse reverses the same object. clean.toString().equals(clean.reverse().toString()) also works (the left side is evaluated first), but a separate variable is more readable.

Common mistakes

Wrong ideaThe truth
s.toUpperCase(); changes sIt returns a new String; s = s.toUpperCase();
substring(2, 4) is 3 charactersb is excluded: 2 characters
s.lengthIn String it is a method: s.length()
'A' + 2 → C67; for a character (char) ('A' + 2)
sb1.equals(sb2) compares contentIn StringBuilder it compares addresses; compare with toString()
split(".") splits at the dotIn a regex the dot is any character; split("\\.")
split keeps trailing empty partsIt drops them; it keeps leading ones
matches("\\d+") asks whether there is a digit in the textIt asks whether the whole text is digits. To search inside, Matcher.find
s.charAt(0) = 'b';Does not compile; String is immutable
s += x in a loop is no problemA new object at each step; use StringBuilder

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed?

String s = "Object Oriented";
System.out.println(s.indexOf('e') + " " + s.lastIndexOf('e'));
System.out.println(s.substring(2, 4) + s.substring(7, 8));
Show answer
3 13 and jeO

Indexes start at 0: O0 b1 j2 e3 c4 t5 (space)6 O7 r8 i9 e10 n11 t12 e13 d14. The first 'e' is at 3, the last 'e' at 13. substring(2, 4) takes 2 and 3 (end excluded): "je". substring(7, 8) takes only 7: "O".

Question 2medium

What is printed?

String t = "java";
t.toUpperCase();
t.concat("!");
System.out.println(t);
Show answer
java

String is immutable. toUpperCase and concat return a new String; they do not change the object t points to. The returned value is lost because it is not assigned anywhere. The correct way is t = t.toUpperCase();.

Question 3medium

What is printed?

System.out.println('A' + 2);
System.out.println((char) ('A' + 2));
System.out.println("A" + 2);
System.out.println('7' - '0');
Show answer
67, C, A2, 7

char is a numeric type: 'A' + 2 is an int addition, 65 + 2 = 67. If you cast the result to char, it is the character number 67: C. "A" is a String, so + concatenates: A2. Subtracting '0' from a digit character gives the value of the digit: 55 − 48 = 7.

Question 4medium

What is printed?

StringBuilder sb = new StringBuilder("code");
sb.append(42).append('!');
sb.insert(0, "my ");
System.out.println(sb);
sb.reverse();
System.out.println(sb);
Show answer
my code42! and !24edoc ym

StringBuilder is mutable; its methods change the same object and return it (so they can be chained). "code" → "code42" → "code42!" → insert "my " at the start: "my code42!". reverse reverses the characters: "!24edoc ym".

Question 5hard

What is printed?

StringBuilder x = new StringBuilder("hi"), y = new StringBuilder("hi");
System.out.println(x.equals(y));
System.out.println(x.toString().equals(y.toString()));
Show answer
false and true

StringBuilder does not override equals; Object's equals compares addresses, two separate objects: false. To compare the content, convert to String first: true. (Since Java 11, x.compareTo(y) == 0 also works.)

Question 6hard

What is printed?

System.out.println(Arrays.toString("a,b,,c,,".split(",")));
System.out.println(Arrays.toString(",x,y".split(",")));
Show answer
[a, b, , c] and [, x, y]

Two commas in a row produce an empty String: a, b, "", c. Empty parts at the end are dropped by split, so the array has 4 elements. An empty part at the start is not dropped: the first element is "".

Question 7medium

What is printed?

System.out.println("202312345".matches("\\d{9}"));
System.out.println("20231234".matches("\\d{9}"));
System.out.println("abc123x".matches("[a-z]+\\d+"));
Show answer
true, false, false

matches requires the whole String to fit the pattern. \\d{9} is exactly 9 digits: the first has 9 digits, the second has 8. [a-z]+\\d+ ends with letters followed by digits; the x at the end does not fit. In a Java String, the regex \d is written with two backslashes, "\\d".

Question 8hard

What is printed?

System.out.println("programming".replaceAll("[aeiou]", "*"));
System.out.println("a1b22c333".replaceAll("\\d+", "#"));
System.out.println("x.y.z".replace(".", "-") + " " + "x.y.z".replaceAll(".", "-"));
Show answer
pr*gr*mm*ng, a#b#c#, x-y-z -----

[aeiou] is any vowel: o, a, i become stars. \\d+ is one or more consecutive digits, and each group becomes a single #. replace changes plain text, while replaceAll takes a regex: in a regex . means any character, so all five characters become dashes.

Question 9easy

What is printed?

String mix = "a1B2 c3!";
int letters = 0, digits = 0, upper = 0;
for (int i = 0; i < mix.length(); i++) {
    char ch = mix.charAt(i);
    if (Character.isLetter(ch)) letters++;
    if (Character.isDigit(ch)) digits++;
    if (Character.isUpperCase(ch)) upper++;
}
System.out.println(letters + " " + digits + " " + upper);
Show answer
3 3 1

Letters: a, B, c (3). Digits: 1, 2, 3 (3). Upper case: B (1). The space and ! do not count in any counter.

Question 10hard

What is printed?

System.out.println("Apple".compareTo("apple"));
System.out.println("cat".compareTo("car"));
System.out.println("ab".compareTo("abcd"));
Show answer
-32, 2, -2

The code difference of the first differing character is returned. 'A' (65) − 'a' (97) = −32: upper case letters come before lower case ones. "cat"/"car": the first difference is t (116) − r (114) = 2. "ab" is the same as the beginning of "abcd"; if there is no difference, the length difference is returned: 2 − 4 = −2.

Question 11medium

Find the compile error(s).

String s = "cat";
s.charAt(0) = 'b';
StringBuilder sb = "hello";
char c = s.substring(0, 1);
Show answer
Three errors: assignment to charAt, String → StringBuilder, String → char

charAt returns a value, not a variable: javac unexpected type; required: variable, found: value. A letter of a String cannot be changed; use s = "b" + s.substring(1); or StringBuilder's setCharAt. A literal cannot be assigned to a StringBuilder: new StringBuilder("hello"). substring always returns a String; for a char use s.charAt(0).

Question 12hard

Write a method capitalizeWords(String text) that returns the words of text separated by single spaces, each with its first letter upper case and the rest lower case. capitalizeWords(" hello wORLD from java ") returns Hello World From Java.

Show answer
trim + split("\\s+") + StringBuilder + Character.toUpperCase

First remove the spaces at the start and end, then split on one or more spaces. Use StringBuilder instead of concatenating Strings in a loop:

public static String capitalizeWords(String text) {
    String[] words = text.trim().split("\\s+");
    StringBuilder sb = new StringBuilder();
    for (String w : words) {
        if (sb.length() > 0) sb.append(' ');
        sb.append(Character.toUpperCase(w.charAt(0)));
        sb.append(w.substring(1).toLowerCase());
    }
    return sb.toString();
}
Without trim, split would produce an empty String at the start and w.charAt(0) would throw StringIndexOutOfBoundsException.

Verification: All code examples and answers were run with JDK 26 (java verify/SEN1002/W08.java, W08b.java); the compile errors were tried with javac (verify/SEN1002/err/E08a-c.java).

Review card

String is IMMUTABLE: s.toUpperCase() alone does nothing → s = s.toUpperCase(); Index from 0. length() is a method (in an array length is a field). charAt(i), last = length()-1. substring(a, b): a included, b EXCLUDED, length b-a. substring(a): to the end. indexOf returns -1 if not found. lastIndexOf searches from the end. equals content, == address. equalsIgnoreCase. compareTo: code difference of the first differing character. char is a number: 'A'+2=67, (char)('A'+2)='C', '7'-'0'=7, 'A'=65 'a'=97 '0'=48. Character: isLetter isDigit isLetterOrDigit isUpperCase isWhitespace toUpperCase. StringBuilder: append insert(i,x) reverse deleteCharAt delete(a,b) replace(a,b,s) setCharAt. StringBuilder methods change the SAME object. equals NOT overridden → toString().equals. split(","): trailing empties are dropped, leading ones stay. split("\\s+"), split("\\.") (escape the dot). matches checks the WHOLE text. \\d digit \\w letter/digit/_ \\s whitespace . anything [abc] one of [a-z] range [^0-9] not * 0+ + 1+ ? 0/1 {n} exactly n {n,m} replace plain text, replaceAll regex: "x.y".replaceAll(".", "-") → "---"