Java17(Java13以降)でgraphemeをカウントする
Java17(Java13以降)でgraphemeをカウントする
Java17(Java13以降)でgraphemeを簡単にカウント出来るようになっていたのでそのメモです。
下記のtweetを見て、Java13以降ではgraphemeを簡単にカウント出来るようになっている情報を知ったので
今更試してみました。
Java 13以降であれば、正規表現を使うのが手っ取り早いです。
— Yuichi Sakuraba (@skrb) 2022年3月25日
String[] chars = text.split("\\b{g}");
int length = chars.length;
下記バージョンで試してみます。
dependency
gradleを使います。
比較に使用するためにICUのBreakIteratorを追加します。
build.gradle
dependencies { implementation("com.ibm.icu:icu4j:71.1") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } }
grapheme
今までJavaでgraphemeをカウントするにはBreakIteratorを使用していました。
さらに、java.textのBreakIteratorは絵文字を正しくカウント出来ないので、
ICUのBreakIteratorの実装を使用していました。
上記のtweetでJava13以降では簡単にgraphemeをカウント出来るようになったので確認してみます。
下記の3パターンでgraphemeを確認してみます。
java.text.BreakIterator
java.text.BreakIterator で grapheme をカウントするために下記のメソッドを用意しておきます。
public static int getGraphemeLength(String value) { final BreakIterator it = BreakIterator.getCharacterInstance(); it.setText(value); int count = 0; while (it.next() != BreakIterator.DONE) { count++; } return count; }
com.ibm.icu.text.BreakIterator
com.ibm.icu.text.BreakIterator で grapheme をカウントするために下記のメソッドを用意しておきます。
Java だと別名 import が出来ないので、完全修飾名で使用します。
public static int getGraphemeLengthWithIcu(String value) { final com.ibm.icu.text.BreakIterator it = com.ibm.icu.text.BreakIterator.getCharacterInstance(); it.setText(value); int count = 0; while (it.next() != BreakIterator.DONE) { count++; } return count; }
Java13+
tweetにあったようにJava13以降では下記のように\b{g}
を利用します。
Oracleのドキュメントを見るとA Unicode extended grapheme cluster boundary(Unicode拡張書記素クラスタ境界)
となっています。
https://docs.oracle.com/javase/jp/17/docs/api/java.base/java/util/regex/Pattern.html
str.split("\\b{g}").length
確認
下記のメインコードで確認してみます。
👨👨👦の絵文字をカウントしてみます。
Main.java
public static void main(String[] args) { // 👨👨👦 final String str = "\uD83D\uDC68\u200D\uD83D\uDC68\u200D\uD83D\uDC66"; System.out.println(str); System.out.println("length: " + str.length()); System.out.println("BreakIterator(java): " + getGraphemeLength(str)); System.out.println("BreakIterator(ICU): " + getGraphemeLengthWithIcu(str)); System.out.println("java13+: " + str.split("\\b{g}").length); }
結果。
Java13+とICUのBreakIteratorではうまくカウント出来ています。
やはりjava.text.BreakIteratorはうまくカウント出来ませんでした。
👨👨👦 length: 8 BreakIterator(java): 5 BreakIterator(ICU): 1 java13+: 1
追記
この記事を書いたあとに maki さんの tweet で skin tone のカウントがおかしいというのを見かけて試してみました。
Java 17での\b{g}はほぼほぼemojiのカウントができるけど、skin tone (🏻 1F3FB - 🏿 1F3FF) のカウントだけ変。バグかな? pic.twitter.com/oTmuiVl3Fr
— Toshiaki Maki 💉💉💉 (@making) 2022年5月4日
同様に下記のコードで確認します。
Main.java
public static void main(String[] args) { // a + 🏻 final String aAndSkinTone = "a" + "\uD83C\uDFFB"; System.out.println(aAndSkinTone); System.out.println("length: " + aAndSkinTone.length()); System.out.println("BreakIterator(java): " + getGraphemeLength(aAndSkinTone)); System.out.println("BreakIterator(ICU): " + getGraphemeLengthWithIcu(aAndSkinTone)); System.out.println("java13+: " + aAndSkinTone.split("\\b{g}").length); }
ICU の BreakIterator でも結果は同じでした。
a🏻 length: 3 BreakIterator(java): 2 BreakIterator(ICU): 1 java13+: 1
おわり。
サンプルコードは下記にあげました。
[参考]