質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.22%

BreakIterator.getCharacterInstanceがロケールを引数に取りますが、どう使われるでしょうか

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,858

yuba

score 5291

Javaで文字列の「人間にとっての文字数」を数えるにはtoString()ではダメです。サロゲートペア文字が2文字にカウントされてしまいます。codePointCount()も惜しいのですがダメです。異体字セレクタ込みの文字や互換分解された文字が複数文字にカウントされてしまいます。

ではどうするかというとBreakIterator.getCharacterInstanceを使って人間にとっての文字をイテレートしてもらうというのが正解になるのですが、このメソッド、Localeを引数に取ります。

同じクラスのgetSentenceInstance(文を列挙)とかgetWordInstance(単語を列挙)とかなら思い切りロケール依存しますんでロケールを引数に取るのもわかるのですが、getCharacterInstanceはロケールをどのように使うのでしょうか。

そして、より直接的な質問の目的なのですが、言語非依存なサーバプログラムを書こうというときにはなんのロケールを指定すべきなんでしょうか。
(getAvailableLocalesの仕様を呼んでいると、USを指定するのが確実なのかとも思いますが)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

 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

import java.text.BreakIterator;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class Main {

    private static final Map<String, Integer> TESTER;
    static {
        Map<String, Integer> tester = new HashMap<String, Integer>();
        tester.put("Julius Cæsar", 12); // 合字
        tester.put("\u0075\u0308\u0304", 1); // 合字
        tester.put("\u30DB\u309A", 1); // 合字
        tester.put("㍿", 1); // 合字
        tester.put("キャリーパミュパミュ", 12); // 半角カナ
        tester.put("㌦亞䖸丿塔", 5); // 旧字・異字体
        tester.put("कवि की उमंग उल्का मुट्ठी", 14); // ヒンドゥー後
        tester.put(
                "\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",
                38); // ヘブライ語、右から読む

        TESTER = Collections.unmodifiableMap(tester);
    }

    private static final String ERROR_MESSAGE = "Locale=%s, text=%s, expected=%d, detected=%d";

    public static void main(String[] args) {
        for (Map.Entry<String, Integer> tester : TESTER.entrySet()) {
            for (Locale locale : Locale.getAvailableLocales()) {
                int count = countCharactor(tester.getKey(), locale);
                assert count == tester.getValue() : String.format(ERROR_MESSAGE,
                        locale.toLanguageTag(), tester.getKey(), tester.getValue(), count);
            }
        }
    }

    private static int countCharactor(String text, Locale locale) {
        BreakIterator iterator = BreakIterator.getCharacterInstance(locale);
        iterator.setText(text);

        int count = 0;
        while (iterator.next() != BreakIterator.DONE) {
            count++;
        }
        return count;
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/10/29 14:15 編集

    「そういう文字」としては、次のようなものがありますね。
    - オランダ語の「IJ」 https://ja.wikipedia.org/wiki/IJ
    - チェコ語やスロバキア語の「Ch」https://ja.wikipedia.org/wiki/Ch
    - スロバキア語の「Dz」
    ユニコードでは、これらの合字に単独の符号を割り当てている場合もありますが、その場合も正規化すると2文字分の符号で表されます。
    もっともこれらの例に挙げた文字については、Javaの標準クラスでもICUで拡張されたほうでも、BreakIteratorでロケール毎の取り扱いをしてはいないようです。

    キャンセル

  • 2016/10/29 16:43

    実例ありがとうございます。
    世界は広いですね、ijは一文字とも二文字ともつかないとか。

    なるほど、これがロケールの必要性ですか。

    キャンセル

  • 2016/10/29 18:02

    ikedas様

    コメントありがとうございます。
    私も勉強させていただきました。

    キャンセル

+1

KiyoshiMotokiさんのご回答、とても好感が持てますね。誠意を感じます。

final Locale[] locales = new Locale[]{
    Locale.US,
    Locale.JAPANESE,
    Locale.forLanguageTag("hi_IN")
};

Stream.of(locales)
    .map(locale->BreakIterator.getCharacterInstance(locale))
    .map(bi->bi.getClass().getName())
    .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/10 03:15 編集

    コードを追って見た限り、最終的にはロケールに関わらず、
    jre/lib/resources.jarのsun.text.resources.CharacterBreakIteratorData
    がロードされるようで、ロケールに依存しないように見えます。

    実際にデバッグしたりしたわけではないので、今のところ断言はできません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる