现有的答案是
- 不正确:他们认为这
char
是一个单独的字符(代码点),而它是一个 UTF-16 单词,可以是代理对的一半,或者
- 使用本身还不错但需要向项目添加依赖项的库,或者
- 使用完全有效但并非总是可行的Java 8 Streams 。
让我们看一下代理字符(每个这样的字符都由两个 UTF-16 单词——Java 组成char
)并且可以有大写和小写的变体:
IntStream.rangeClosed(0x01_0000, 0x10_FFFF)
.filter(ch -> Character.toUpperCase(ch) != Character.toLowerCase(ch))
.forEach(ch -> System.out.print(new String(new int[] { ch }, 0, 1)));
它们中的许多可能看起来像“豆腐”(□),但它们大多是稀有脚本的有效字符,并且某些字体支持它们。
例如,我们看一下 Deseret Small Letter Long I (), U+10428, "\uD801\uDC28"
:
System.out.println("U+" + Integer.toHexString(
"\uD801\uDC28".codePointAt(0)
)); // U+10428
System.out.println("U+" + Integer.toHexString(
Character.toTitleCase("\uD801\uDC28".codePointAt(0))
)); // U+10400 — ok! capitalized character is another code point
System.out.println("U+" + Integer.toHexString(new String(new char[] {
Character.toTitleCase("\uD801\uDC28".charAt(0)), "\uD801\uDC28".charAt(1)
}).codePointAt(0))); // U+10428 — oops! — cannot capitalize an unpaired surrogate
因此,即使在不能大写的情况下,也可以将代码点大写char
。考虑到这一点,让我们编写一个正确的(并且兼容 Java 1.5!)大写字母:
@Contract("null -> null")
public static CharSequence capitalize(CharSequence input) {
int length;
if (input == null || (length = input.length()) == 0) return input;
return new StringBuilder(length)
.appendCodePoint(Character.toTitleCase(Character.codePointAt(input, 0)))
.append(input, Character.offsetByCodePoints(input, 0, 1), length);
}
并检查它是否有效:
public static void main(String[] args) {
// ASCII
System.out.println(capitalize("whatever")); // w -> W
// UTF-16, no surrogate
System.out.println(capitalize("что-то")); // ч -> Ч
// UTF-16 with surrogate pairs
System.out.println(capitalize("\uD801\uDC28")); // ->
}
也可以看看: