Home/Java/Strings and Unicode

Strings and Unicode

UTF-16 units, code points, normalization, and safe indexing

Text has several lengths

String is immutable UTF-16 text. length() counts 16-bit char units. Supplementary code points use surrogate pairs, so one visible symbol may occupy two chars. A code point is still not a grapheme cluster: a base letter plus a combining mark can form one displayed character with two code points. Neither char indexing nor code-point indexing alone implements user-visible cursor movement.

java
String text = "A😀é";
assert text.length() == 5;
assert text.codePointCount(0, text.length()) == 4;

Use codePoints when the contract is defined over code points; use offsetByCodePoints to translate boundaries back into UTF-16 substring indices. Splitting a surrogate pair produces ill-formed text even though String can store it. This course's text exercises assume well-formed UTF-16 inputs unless a specific lesson says otherwise.

Equality and transformations

String.equals compares the exact encoded sequence. Canonically equivalent text may differ (é versus e plus combining accent). Normalization is a deliberate domain choice; Normalizer.normalize(text, Form.NFC) changes representation. Case conversion can expand characters and depends on locale, so normalize complete strings before iterating transformed code points. Locale.ROOT avoids machine-locale dependence for protocol-oriented rules, but lowercase is not a universal casefold.

java
String composed = "é";
String decomposed = "é";
assert !composed.equals(decomposed);
assert java.text.Normalizer.normalize(decomposed,
       java.text.Normalizer.Form.NFC).equals(composed);

Repeated concatenation in a loop can copy an ever-growing prefix and become quadratic. StringBuilder supports incremental construction, while String.join expresses joining existing pieces. substring creates a new string on current JDKs; do not assume it is a zero-copy view. Charset conversion is another boundary: explicitly choose UTF-8 rather than a platform default, and decide how malformed byte sequences should be handled.

Put it into practice

Implement the exercise in a full-screen workspace, then run its tests.

Open practice
Next lessonMethods, overloads, and generics