Chapter 9: Strings and Character Processing
9.01Beginner / Intermediate7 topics16 min4 interactive animations

String fundamentals

How Java represents text: chars versus Strings, ASCII, Unicode, UTF-16 code units and code points, immutability and the string pool. Understanding these prevents the classic bugs with emoji, accents and string comparison.

Learning goals

  • Explain the difference between a char and a String
  • Convert between characters and their numeric codes
  • Distinguish UTF-16 code units from Unicode code points
  • Explain immutability and the string pool

Prerequisites

Arrays and dynamic arrays
9.01.1

Characters vs strings

A char is a single 16-bit value, written with single quotes ('a'); a String is an immutable sequence of chars, written with double quotes ("a"). They are different types: 'a' is a number-like primitive, "a" is an object. A String is conceptually a char[] with methods, but you cannot modify it in place.

s.charAt(i) returns a char; String.valueOf(c) or "" + c turns a char into a String.

Java
1char c = 'a';
2String s = "abc";
3char first = s.charAt(0); // 'a'
4String one = String.valueOf(c); // "a"
5char[] letters = s.toCharArray(); // a mutable copy: {'a', 'b', 'c'}
6String back = new String(letters); // "abc"
9.01.2

ASCII

ASCII assigns numbers 0 to 127 to English letters, digits, punctuation and control characters: '0' to '9' are 48 to 57, 'A' to 'Z' are 65 to 90 and 'a' to 'z' are 97 to 122. Because letters and digits are consecutive, c - 'a' is a letter's index and c - '0' a digit's value, and upper and lower case differ by 32.

Step through the string to see each character's code and the XOR 32 case trick.

Java
1int idx = 'q' - 'a'; // 16
2int digit = '7' - '0'; // 7
3char upper = (char) ('g' - 32); // 'G'
4boolean isLower = c >= 'a' && c <= 'z';

A char is a number: codes, c - 'a' and case flipping

Interactive · edit the input

Your input

Try

char[] s · length 5

a
Z
5
␠
b
01234
Being comparedAlready checked
Setup

Every char is a number. In ASCII 'a' = 97, 'A' = 65, '0' = 48. Subtracting a base character gives an index.

Step 1 / 7
Java
1for (int i = 0; i < s.length(); i++) {
2 char c = s.charAt(i);
3 int code = c; // widening: the character's numeric code
4 int index = c - 'a'; // 0..25 for lowercase letters
5 int digit = c - '0'; // 0..9 for digits
6}
7// toggle ASCII case: flip bit 5 (value 32); letters only
8char flipped = (char) (c ^ 32); // 'a' (97) <-> 'A' (65)

Live state

length

5

No condition evaluated on this step
Execution trace (dry run table)Show 7 steps
StepLineStateWhat happens
1L1length=5Every char is a number. In ASCII 'a' = 97, 'A' = 65, '0' = 48. Subtracting a base character gives an index.
2L4i=0 c='a' code=97 c - 'a'=0 c - '0'=-'a' is 97; 97 - 97 = 0 is its position in the alphabet.
3L3i=1 c='Z' code=90 c - 'a'=- c - '0'=-'Z' is 90, an uppercase letter (65-90).
4L5i=2 c='5' code=53 c - 'a'=- c - '0'=5'5' is 53; 53 - 48 = 5 is the digit's value.
5L3i=3 c=' ' code=32 c - 'a'=- c - '0'=-' ' is 32, not a letter or digit.
6L4i=4 c='b' code=98 c - 'a'=1 c - '0'=-'b' is 98; 98 - 97 = 1 is its position in the alphabet.
7L6return=[97, 90, 53, 32, 98]Characters are compared, sorted and used as array indexes through these numeric codes.
9.01.3

Unicode

Unicode is the universal character set: it assigns every character a number called a code point (U+0041 is 'A', U+00E9 is 'é', U+1F600 is a smiling emoji). There are over a million code points. ASCII is just the first 128 of them. How code points are stored in bytes is a separate matter: that is the encoding (UTF-8, UTF-16, ...).

Enter an emoji in the animation and compare the number of code units with the number of code points.

Java
1int cp = "é".codePointAt(0); // 233 = U+00E9
2String smile = new String(Character.toChars(0x1F600));
3int len = smile.length(); // 2: two UTF-16 units
4int chars = smile.codePointCount(0, len); // 1: one character

UTF-16 code units vs Unicode code points

Interactive · edit the input

Your input

Try

UTF-16 code units (hex) · length 5

0048
0069
D83D
DE00
00E9
01234
One code unit = one code pointSurrogate pair = one code pointAlready read
Setup

Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.

Step 1 / 6
Java
1int units = s.length(); // UTF-16 code units
2int count = s.codePointCount(0, units); // real characters
3for (int i = 0; i < units; ) {
4 int cp = s.codePointAt(i); // combines a surrogate pair into one code point
5 i += Character.charCount(cp); // 2 for supplementary characters, otherwise 1
6}

Live state

s.length()

5

No condition evaluated on this step
Execution trace (dry run table)Show 6 steps
StepLineStateWhat happens
1L1s.length()=5Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.
2L4i=0 cp=U+48 charCount(cp)=1Unit 48 is a whole character (U+48).
3L4i=1 cp=U+69 charCount(cp)=1Unit 69 is a whole character (U+69).
4L4i=2 cp=U+1F600 charCount(cp)=2Units D83D and DE00 form a surrogate pair: one character, U+1F600. charAt(2) alone would return half of it.
5L4i=4 cp=U+E9 charCount(cp)=1Unit E9 is a whole character (U+E9).
6L6s.length()=5 codePointCount=4 return=5 units, 4 code points5 code units but 4 code points. Use codePointCount and codePoints() when you mean characters.
9.01.4

UTF-16 code units

Java strings are sequences of UTF-16 code units, each 16 bits. Code points up to U+FFFF (the Basic Multilingual Plane) take one unit. Larger code points, such as most emoji, are encoded as a surrogate pair of two units. length(), charAt and substring all work in units, not in characters.

The hex cells in the animation are the actual code units.

CharacterCode pointUTF-16 unitslength()
AU+004100411
éU+00E900E91
日U+65E565E51
😀U+1F600D83D DE002

UTF-16 code units vs Unicode code points

Interactive · edit the input

Your input

Try

UTF-16 code units (hex) · length 5

0048
0069
D83D
DE00
00E9
01234
One code unit = one code pointSurrogate pair = one code pointAlready read
Setup

Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.

Step 1 / 6
Java
1int units = s.length(); // UTF-16 code units
2int count = s.codePointCount(0, units); // real characters
3for (int i = 0; i < units; ) {
4 int cp = s.codePointAt(i); // combines a surrogate pair into one code point
5 i += Character.charCount(cp); // 2 for supplementary characters, otherwise 1
6}

Live state

s.length()

5

No condition evaluated on this step
Execution trace (dry run table)Show 6 steps
StepLineStateWhat happens
1L1s.length()=5Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.
2L4i=0 cp=U+48 charCount(cp)=1Unit 48 is a whole character (U+48).
3L4i=1 cp=U+69 charCount(cp)=1Unit 69 is a whole character (U+69).
4L4i=2 cp=U+1F600 charCount(cp)=2Units D83D and DE00 form a surrogate pair: one character, U+1F600. charAt(2) alone would return half of it.
5L4i=4 cp=U+E9 charCount(cp)=1Unit E9 is a whole character (U+E9).
6L6s.length()=5 codePointCount=4 return=5 units, 4 code points5 code units but 4 code points. Use codePointCount and codePoints() when you mean characters.
9.01.5

Code points

A code point is a whole Unicode character. Use codePointAt(i), codePointCount, codePoints() and Character.charCount(cp) when you mean characters rather than units. Character.isLetter(int) has an overload that takes a code point.

Walk through a string with an emoji and see one code point cover two units.

Java
1String s = "Hi��";
2int units = s.length(); // 4
3int points = s.codePointCount(0, units); // 3
4int[] cps = s.codePoints().toArray(); // {72, 105, 128512}

UTF-16 code units vs Unicode code points

Interactive · edit the input

Your input

Try

UTF-16 code units (hex) · length 5

0048
0069
D83D
DE00
00E9
01234
One code unit = one code pointSurrogate pair = one code pointAlready read
Setup

Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.

Step 1 / 6
Java
1int units = s.length(); // UTF-16 code units
2int count = s.codePointCount(0, units); // real characters
3for (int i = 0; i < units; ) {
4 int cp = s.codePointAt(i); // combines a surrogate pair into one code point
5 i += Character.charCount(cp); // 2 for supplementary characters, otherwise 1
6}

Live state

s.length()

5

No condition evaluated on this step
Execution trace (dry run table)Show 6 steps
StepLineStateWhat happens
1L1s.length()=5Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.
2L4i=0 cp=U+48 charCount(cp)=1Unit 48 is a whole character (U+48).
3L4i=1 cp=U+69 charCount(cp)=1Unit 69 is a whole character (U+69).
4L4i=2 cp=U+1F600 charCount(cp)=2Units D83D and DE00 form a surrogate pair: one character, U+1F600. charAt(2) alone would return half of it.
5L4i=4 cp=U+E9 charCount(cp)=1Unit E9 is a whole character (U+E9).
6L6s.length()=5 codePointCount=4 return=5 units, 4 code points5 code units but 4 code points. Use codePointCount and codePoints() when you mean characters.
9.01.6

Immutability

A String cannot change after creation: every operation such as concat, replace, substring or toUpperCase returns a new String. This makes strings safe to share between threads, safe as hash keys (the hash code can be cached) and enables the string pool. The price is that building a string by repeated concatenation copies characters every time.

Use StringBuilder when you build or edit text in a loop.

Java
1String s = "cat";
2String t = s.toUpperCase(); // new object "CAT"
3s.concat("s"); // result ignored: s is still "cat"
4System.out.println(s); // cat
9.01.7

String pooling

String literals are interned: identical literals share one object in the string pool, so "java" == "java" is true. Strings created with new or at runtime are separate objects, so == compares references and can be false even for equal text. Always compare strings with equals. intern() returns the pooled instance.

Compile-time constant expressions such as "ja" + "va" are also pooled.

Java
1String a = "java";
2String b = "java";
3String c = new String("java");
4boolean same = (a == b); // true: both are the pooled literal
5boolean ref = (a == c); // false: c is a new object
6boolean eq = a.equals(c); // true
7boolean pooled = (a == c.intern()); // true

Practice ladder

Now try it yourself

Easy

  • What is the code of 'A'? How do you get from 'A' to 'a'?
  • What does "😀".length() return, and why?

Medium

  • Explain why s.concat("x") alone does not change s
  • Predict a == b for new String("x") and "x"

Advanced

  • Count the number of Unicode characters (not units) in a string that contains emoji

Key takeaways

  • A char is a 16-bit code unit; a String is an immutable sequence of them.
  • length() counts UTF-16 units, not characters; use code points for characters.
  • Compare strings with equals; == compares references.