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
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.
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"
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.
1int idx = 'q' - 'a'; // 162int digit = '7' - '0'; // 73char 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 inputchar[] s · length 5
Every char is a number. In ASCII 'a' = 97, 'A' = 65, '0' = 48. Subtracting a base character gives an index.
1for (int i = 0; i < s.length(); i++) {2char c = s.charAt(i);3int code = c; // widening: the character's numeric code4int index = c - 'a'; // 0..25 for lowercase letters5int digit = c - '0'; // 0..9 for digits6}7// toggle ASCII case: flip bit 5 (value 32); letters only8char flipped = (char) (c ^ 32); // 'a' (97) <-> 'A' (65)
Live state
length
5
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | length=5 | Every char is a number. In ASCII 'a' = 97, 'A' = 65, '0' = 48. Subtracting a base character gives an index. |
| 2 | L4 | i=0 c='a' code=97 c - 'a'=0 c - '0'=- | 'a' is 97; 97 - 97 = 0 is its position in the alphabet. |
| 3 | L3 | i=1 c='Z' code=90 c - 'a'=- c - '0'=- | 'Z' is 90, an uppercase letter (65-90). |
| 4 | L5 | i=2 c='5' code=53 c - 'a'=- c - '0'=5 | '5' is 53; 53 - 48 = 5 is the digit's value. |
| 5 | L3 | i=3 c=' ' code=32 c - 'a'=- c - '0'=- | ' ' is 32, not a letter or digit. |
| 6 | L4 | i=4 c='b' code=98 c - 'a'=1 c - '0'=- | 'b' is 98; 98 - 97 = 1 is its position in the alphabet. |
| 7 | L6 | return=[97, 90, 53, 32, 98] | Characters are compared, sorted and used as array indexes through these numeric codes. |
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.
1int cp = "é".codePointAt(0); // 233 = U+00E92String smile = new String(Character.toChars(0x1F600));3int len = smile.length(); // 2: two UTF-16 units4int chars = smile.codePointCount(0, len); // 1: one character
UTF-16 code units vs Unicode code points
Interactive · edit the inputUTF-16 code units (hex) · length 5
Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.
1int units = s.length(); // UTF-16 code units2int count = s.codePointCount(0, units); // real characters3for (int i = 0; i < units; ) {4int cp = s.codePointAt(i); // combines a surrogate pair into one code point5i += Character.charCount(cp); // 2 for supplementary characters, otherwise 16}
Live state
s.length()
5
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | s.length()=5 | Java strings store UTF-16 code units. s.length() counts units, not characters: here 5. |
| 2 | L4 | i=0 cp=U+48 charCount(cp)=1 | Unit 48 is a whole character (U+48). |
| 3 | L4 | i=1 cp=U+69 charCount(cp)=1 | Unit 69 is a whole character (U+69). |
| 4 | L4 | i=2 cp=U+1F600 charCount(cp)=2 | Units D83D and DE00 form a surrogate pair: one character, U+1F600. charAt(2) alone would return half of it. |
| 5 | L4 | i=4 cp=U+E9 charCount(cp)=1 | Unit E9 is a whole character (U+E9). |
| 6 | L6 | s.length()=5 codePointCount=4 return=5 units, 4 code points | 5 code units but 4 code points. Use codePointCount and codePoints() when you mean characters. |
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.
| Character | Code point | UTF-16 units | length() |
|---|---|---|---|
| A | U+0041 | 0041 | 1 |
| é | U+00E9 | 00E9 | 1 |
| 日 | U+65E5 | 65E5 | 1 |
| 😀 | U+1F600 | D83D DE00 | 2 |
UTF-16 code units vs Unicode code points
Interactive · edit the inputUTF-16 code units (hex) · length 5
Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.
1int units = s.length(); // UTF-16 code units2int count = s.codePointCount(0, units); // real characters3for (int i = 0; i < units; ) {4int cp = s.codePointAt(i); // combines a surrogate pair into one code point5i += Character.charCount(cp); // 2 for supplementary characters, otherwise 16}
Live state
s.length()
5
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | s.length()=5 | Java strings store UTF-16 code units. s.length() counts units, not characters: here 5. |
| 2 | L4 | i=0 cp=U+48 charCount(cp)=1 | Unit 48 is a whole character (U+48). |
| 3 | L4 | i=1 cp=U+69 charCount(cp)=1 | Unit 69 is a whole character (U+69). |
| 4 | L4 | i=2 cp=U+1F600 charCount(cp)=2 | Units D83D and DE00 form a surrogate pair: one character, U+1F600. charAt(2) alone would return half of it. |
| 5 | L4 | i=4 cp=U+E9 charCount(cp)=1 | Unit E9 is a whole character (U+E9). |
| 6 | L6 | s.length()=5 codePointCount=4 return=5 units, 4 code points | 5 code units but 4 code points. Use codePointCount and codePoints() when you mean characters. |
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.
1String s = "Hi��";2int units = s.length(); // 43int points = s.codePointCount(0, units); // 34int[] cps = s.codePoints().toArray(); // {72, 105, 128512}
UTF-16 code units vs Unicode code points
Interactive · edit the inputUTF-16 code units (hex) · length 5
Java strings store UTF-16 code units. s.length() counts units, not characters: here 5.
1int units = s.length(); // UTF-16 code units2int count = s.codePointCount(0, units); // real characters3for (int i = 0; i < units; ) {4int cp = s.codePointAt(i); // combines a surrogate pair into one code point5i += Character.charCount(cp); // 2 for supplementary characters, otherwise 16}
Live state
s.length()
5
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | s.length()=5 | Java strings store UTF-16 code units. s.length() counts units, not characters: here 5. |
| 2 | L4 | i=0 cp=U+48 charCount(cp)=1 | Unit 48 is a whole character (U+48). |
| 3 | L4 | i=1 cp=U+69 charCount(cp)=1 | Unit 69 is a whole character (U+69). |
| 4 | L4 | i=2 cp=U+1F600 charCount(cp)=2 | Units D83D and DE00 form a surrogate pair: one character, U+1F600. charAt(2) alone would return half of it. |
| 5 | L4 | i=4 cp=U+E9 charCount(cp)=1 | Unit E9 is a whole character (U+E9). |
| 6 | L6 | s.length()=5 codePointCount=4 return=5 units, 4 code points | 5 code units but 4 code points. Use codePointCount and codePoints() when you mean characters. |
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.
1String s = "cat";2String t = s.toUpperCase(); // new object "CAT"3s.concat("s"); // result ignored: s is still "cat"4System.out.println(s); // cat
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.
1String a = "java";2String b = "java";3String c = new String("java");4boolean same = (a == b); // true: both are the pooled literal5boolean ref = (a == c); // false: c is a new object6boolean eq = a.equals(c); // true7boolean 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.