Javaで文字列の「人間にとっての文字数」を数えるにはtoString()ではダメです。サロゲートペア文字が2文字にカウントされてしまいます。codePointCount()も惜しいのですがダメです。異体字セレクタ込みの文字や互換分解された文字が複数文字にカウントされてしまいます。
ではどうするかというとBreakIterator.getCharacterInstanceを使って人間にとっての文字をイテレートしてもらうというのが正解になるのですが、このメソッド、Localeを引数に取ります。
同じクラスのgetSentenceInstance(文を列挙)とかgetWordInstance(単語を列挙)とかなら思い切りロケール依存しますんでロケールを引数に取るのもわかるのですが、getCharacterInstanceはロケールをどのように使うのでしょうか。
そして、より直接的な質問の目的なのですが、言語非依存なサーバプログラムを書こうというときにはなんのロケールを指定すべきなんでしょうか。
(getAvailableLocalesの仕様を呼んでいると、USを指定するのが確実なのかとも思いますが)
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
ベストアンサー
getCharacterInstanceはロケールをどのように使うのでしょうか。
getCharacterInstance
メソッドの戻り値であるBreakIterator
クラスのAPIリファレンスにある、以下の記述が回答になるかと思います。
https://docs.oracle.com/javase/jp/8/docs/api/java/text/BreakIterator.html
返される境界は、補助文字、結合文字シーケンス、または合字クラスタの境界になる場合があります。たとえば、アクセント付きの文字は、基準文字と発音区別符号として格納されている場合があります。ユーザーの文字に対する認識は言語間で異なります。
で、ウィキペディアの"合字"に関する項を読んでみると、以下のように書いてあります。
https://ja.wikipedia.org/wiki/%E5%90%88%E5%AD%97
合字(ごうじ)またはリガチャー(英: Ligature)とは、複数の文字を合成して一文字にしたもの。
(中略)
インド系文字のほとんどは複数の文字を合成して一音節の音(言語によっては複数の音節で読まれる)を表す字を作るシステムになっている。文字コード上では合字は一部のもの(ॐ などの表意文字として機能する字等)を除いて単独の文字として存在せず、複数の特定の文字を決まった順番で並べた際に1文字の合字として扱われるシステムになっている。
このような文字を正しく「1文字」と判定するためには、やはりロケールを指定してやる必要があるのではないでしょうか?
なぜなら、例えば
「複数のロケールで使用されている文字体系の中に、ある特定のロケールにだけ存在する合字」
とうものも、可能性としてあり得ると思うからです。
言語非依存なサーバプログラムを書こうというときにはなんのロケールを指定すべきなんでしょうか。
上記の理由から、これには正解は無いように思います。
getAvailableLocales
メソッドの
これには、Locale.USと等価なLocaleインスタンスが少なくとも1つ含まれている必要があります。
という記述も、おそらくJavaランタイムが最低限、サポートしなければならないロケールがen_US
だからではないかと推測します。
http://www.oracle.com/technetwork/articles/javase/locale-140624.html#supported
there is no requirement that all runtime implementations support the same set of locales. But all implementations must support a minimal list of them. This list is quite short: English (U.S.).
すべてのランタイム実装は同じロケールのセットをサポートしている必要はありません。 しかし、すべての実装は最小限のリストをサポートしている必要があります。 このリストは非常に短いです:英語(米国)
ちなみに、本回答にあたりgetCharacterInstance
メソッドのソースコードを読んでみようとしましたが、私には理解不可能でした(^^;
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/text/BreakIterator.java#BreakIterator.getCharacterInstance%28java.util.Locale%29
2016/07/11 追記
他のものと異なる文字数をカウントするロケールを検出するプログラム。
-enableassertions
オプションを付けて実行してください。
http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html
java
1import java.text.BreakIterator; 2import java.util.Collections; 3import java.util.HashMap; 4import java.util.Locale; 5import java.util.Map; 6 7public class Main { 8 9 private static final Map<String, Integer> TESTER; 10 static { 11 Map<String, Integer> tester = new HashMap<String, Integer>(); 12 tester.put("Julius Cæsar", 12); // 合字 13 tester.put("\u0075\u0308\u0304", 1); // 合字 14 tester.put("\u30DB\u309A", 1); // 合字 15 tester.put("㍿", 1); // 合字 16 tester.put("キャリーパミュパミュ", 12); // 半角カナ 17 tester.put("㌦亞䖸丿塔", 5); // 旧字・異字体 18 tester.put("कवि की उमंग उल्का मुट्ठी", 14); // ヒンドゥー後 19 tester.put( 20 "\u05DC\u05B4\u05D4\u05B0\u05D9\u05D5\u05B9\u05EA\u0020\u05E2\u05B7\u05DD\u0020\u05D7\u05B8\u05E4\u05B0\u05E9\u05B4\u05C1\u05D9\u0020\u05D1\u05B0\u05BC\u05D0\u05B7\u05E8\u05B0\u05E6\u05B5\u05E0\u05D5\u05BC\u0020\u05D0\u05B6\u05E8\u05B6\u05E5\u0020\u05E6\u05B4\u05D9\u05BC\u05D5\u05B9\u05DF\u0020\u05D5\u05B4\u05D9\u05E8\u05D5\u05BC\u05E9\u05B8\u05C1\u05DC\u05B7\u05D9\u05B4\u05DD", 21 38); // ヘブライ語、右から読む 22 23 TESTER = Collections.unmodifiableMap(tester); 24 } 25 26 private static final String ERROR_MESSAGE = "Locale=%s, text=%s, expected=%d, detected=%d"; 27 28 public static void main(String[] args) { 29 for (Map.Entry<String, Integer> tester : TESTER.entrySet()) { 30 for (Locale locale : Locale.getAvailableLocales()) { 31 int count = countCharactor(tester.getKey(), locale); 32 assert count == tester.getValue() : String.format(ERROR_MESSAGE, 33 locale.toLanguageTag(), tester.getKey(), tester.getValue(), count); 34 } 35 } 36 } 37 38 private static int countCharactor(String text, Locale locale) { 39 BreakIterator iterator = BreakIterator.getCharacterInstance(locale); 40 iterator.setText(text); 41 42 int count = 0; 43 while (iterator.next() != BreakIterator.DONE) { 44 count++; 45 } 46 return count; 47 } 48}
投稿2016/07/08 14:43
編集2016/07/10 16:11総合スコア4791
0
KiyoshiMotokiさんのご回答、とても好感が持てますね。誠意を感じます。
java
1final Locale[] locales = new Locale[]{ 2 Locale.US, 3 Locale.JAPANESE, 4 Locale.forLanguageTag("hi_IN") 5}; 6 7Stream.of(locales) 8 .map(locale->BreakIterator.getCharacterInstance(locale)) 9 .map(bi->bi.getClass().getName()) 10 .forEach(System.out::println);
実行結果は、次の通りでした。
sun.util.locale.provider.RuleBasedBreakIterator sun.util.locale.provider.RuleBasedBreakIterator sun.util.locale.provider.RuleBasedBreakIterator
sun.util.locale.provider.RuleBasedBreakIteratorをデコンパイルして調べるのが手っ取り早そうです。
投稿2016/07/09 12:35
退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/07/08 23:43
2016/07/10 16:11
2016/10/29 05:19 編集
2016/10/29 07:43
2016/10/29 09:02