Java中Character类处理Unicode补充字符需用码点(int)而非char,因char仅支持BMP;应使用codePointAt()、isSurrogatePair()、toCodePoint()等方法安全操作代理对。

Java 中的 Character 类对 Unicode 补充字符(即码点大于 0xFFFF 的字符,如 emoji、部分汉字、古文字等)不能直接用 char 表示,因为 char 是 16 位,只能表示基本多文种平面(BMP)内的字符。补充字符在 UTF-16 编码中以**代理对(surrogate pair)**形式存储:一个高位代理(high surrogate,范围 0xD800–0xDBFF)加一个低位代理(low surrogate,范围 0xDC00–0xDFFF)共同表示一个码点。
区分 char 和码点(code point)是关键
char 是 16 位无符号整数,仅能表示 BMP 字符(U+0000 到 U+FFFF)。补充字符(如 U+1F600 ?、U+20000 ?)必须用 int 类型的码点(0 到 0x10FFFF)来完整表达。Java 提供了专门支持码点的操作方法,避免误把代理对拆成两个“字符”处理。
- 不要用
String.charAt(i)遍历可能含补充字符的字符串——它返回的是单个char,遇到代理对会拆开,导致逻辑错误 - 改用
String.codePointAt(i)获取当前位置的完整码点;配合Character.charCount(int cp)判断该码点占几个char(1 或 2)来跳过整个代理对 - 遍历时推荐用
String.codePoints()(返回IntStream)或String.iterateCodePoints()(Java 21+)
判断和生成代理对要靠 Character 的静态工具方法
手动计算代理对容易出错,Character 提供了安全可靠的转换工具:
-
Character.isSurrogate(char c):判断一个char是否为代理区字符(高或低) -
Character.isHighSurrogate(char c)/Character.isLowSurrogate(char c):分别判断是否为高位/低位代理 -
Character.isSurrogatePair(char high, char low):检查两个char是否构成合法代理对 -
Character.toCodePoint(char high, char low):将合法代理对转为对应码点(int) -
Character.highSurrogate(int cp)和Character.lowSurrogate(int cp):将码点拆为代理对
例如:Character.toCodePoint('\uD83D', '\uDE00') 返回 0x1F600(? 的码点);反过来,Character.highSurrogate(0x1F600) 返回 '\uD83D'。
立即学习“Java免费学习笔记(深入)”;
常见操作示例:安全遍历与计数
以下代码正确统计字符串中“字符数”(按用户感知的字形,即码点数,而非 char 数):
String s = "Hello ? 世界\u20000"; // 含 emoji 和补充汉字
int codePointCount = 0;
for (int i = 0; i < s.length(); ) {
int cp = s.codePointAt(i);
codePointCount++;
i += Character.charCount(cp); // 跳过 1 或 2 个 char
}
System.out.println(codePointCount); // 输出 9(不是 11)
也可用流式写法(更简洁):
long count = s.codePoints().count(); // 直接得码点总数
注意 String.length() 和 StringBuilder 的陷阱
String.length() 返回的是 char 个数,不是“字符”个数。对含补充字符的字符串,它一定 ≥ 码点数(因为每个补充字符占 2 个 char)。类似地,StringBuilder.appendCodePoint(int cp) 比 append(char) 更安全;构造时优先用 String.valueOf(int cp) 而非拼接代理对。
- 错误:
new String(new char[] {'\uD83D', '\uDE00'})—— 可能被某些 API 当作无效序列处理 - 正确:
String.valueOf(Character.toCodePoint('\uD83D', '\uDE00'))或String.valueOf(0x1F600)


















