[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事では、特定の文字の文字コード(コード・ポイント)を取得する方法について解説します。
文字がサロゲートペアかどうかで対応を変える
Javaにおける特定の文字の文字コードは、コード・ポイントと呼びます(つまり、Scalaでもそう呼びます)。
文字のコード・ポイントを取得する方法には、以下の二つがあります。
String#codePointAt
を使用する方法Char#toInt
を使用する方法
通常の場合、つまり文字が「サロゲートペア」かもしれない場合には、 1. のString#codePointAt
を使用します(※サロゲートペアについては後ほど説明します)。
文字がサロゲートペアではないことが保証されている場合には、 2. のChar#toInt
を使用する方法で取得しても構いません。
サンプルとして以下の文字を用意しました。
どちらも「よし」ですが、ひとつめは一般的な「よし」、ふたつめはいわゆる「つちよし」です。
つちよしはサロゲートペアです。
これらの文字のコード・ポイントを取得してみましょう。
val character = "吉" val surrogateCharacter = "𠮷"
通常の場合は codePointAt
メソッドを使う
通常の場合、つまり文字がサロゲートペアかもしれない場合にはString#codePointAt
メソッドを使用しましょう。
javapublic int codePointAt(int index)
codePointAt
メソッドは、指定したインデックスに位置する文字のコード・ポイントを返します。
codePointAt
メソッドを使用したサンプルコードはこちらです。
println( character.codePointAt(0) ) println( surrogateCharacter.codePointAt(0) )
コード・ポイントを調べたい文字はいずれも文字列の1文字目なので、インデックスには0
を指定しています。
実行結果は以下のようになります。
21513 134071
「よし」のコード・ポイント21513
、「つちよし」のコード・ポイント134071
が出力されました。
もしインデックスに指定した数値が文字列のインデックスのとりうる範囲を逸脱する場合、つまり負の数や文字列の長さ以上の値を指定した場合には、java.lang.IndexOutOfBoundsException
が投げられます。
範囲を逸脱しないよう注意してください。
java.lang.String#codePointAt(int)
サロゲートペアは Char
2個でString
1文字を表すイレギュラーな文字
ここで、後回しにしていた「サロゲートペア」についての説明をします。
そもそもサロゲートペアとは何でしょうか?
Javaの文字列の内部表現はUTF-16です。
サロゲートペアとは、UTF-16において「2文字分のデータ量で1文字を表す」ような文字のことをいいます。
UTF-16は、当初16ビットで1文字を表すこととしていました。
しかし16ビットで表現できる文字の数よりも表現したい文字の方が多かったため、この16ビットに入りきらなかった文字に関しては、2文字分の32ビットで表せるよう仕様が拡張されました。
この入りきらなかった文字こそがサロゲートペアです。
Javaにおいては、サロゲートペアはChar
2個でString
1文字を表すイレギュラーな文字として存在しています。
なぜサロゲートペアに注意しないといけないの?
とはいえ、内部表現の話がなぜ表に出てくるのか不思議に思う方もいると思います。
実はJavaの文字列の内部表現(Char
の中身)は拡張前のUTF-16なのです。
つまりChar
には16ビット、1文字分のデータしか入っていません。
1文字分のデータしか持っていないChar
を使って拡張後のUTF-16に対応するための機能が別途用意されたわけです。
そして、その機能の一つがcodePointAt
である、というわけです。
Javaでサロゲートペアの話が出るのはこういう理由があったわけです。
サロゲートペアを含まない場合は head
toInt
メソッドを組み合わせることもできる
さて、Javaの文字はそんな複雑な事情を抱えているのですが、サロゲートペアを含まない場合にはもう少し簡便にコード・ポイントを調べる方法があります。
まずString
をChar
に変換し、これをChar#toInt
メソッドでChar
内部のコード・ポイントを直接取得する方法です。
サンプルコードはこちらです。
println( character.head.toInt ) println( surrogateCharacter.head.toInt )
String
の1文字目はhead
メソッドで取得することができます。
戻り値はChar
なので、toInt
メソッドを使用すればコード・ポイントが取得できます。
実行結果は以下のようになります。
21513 55362
「よし」については、サロゲートペアではないので正しく取得できています。
「つちよし」はサロゲートペアなので、codePointAt
メソッドの実行結果とは異なる値が出力されています。
これは2文字分のデータのうち、前方の1文字分である「上位サロゲート」が取得されてしまったためです。
この値はほんとうに「つちよし」ではないのでしょうか?調べてみましょう。
println( surrogateCharacter.head.toString )
Char#toString
メソッドを使用すると、そのコード・ポイントが実際に表す文字を取得することができます。
出力は以下のようになります。
?
?
が出力されました。
つちよしの上位サロゲート55362
に対応する文字が存在しないため、文字化けしてしまったということです。
サロゲートペアに対してはこの手法を使わないよう十分ご注意ください。
scala.collection.StringOps#head
scala.Char#toInt
まとめ
基本的には、とくにサロゲートペアを含む場合には codePointAt
メソッドを使いましょう。
もしサロゲートペアを含まないことが保証されている場合には、head
メソッドとtoInt
メソッドを組み合わせることで同様の処理をすることができます。
備考:文字列を他の特定の文字セットに変換するには
「文字コード」と表記するとどうしてもASCIIやShift-JISやANSIなどを思い浮かべることが多いと思います。
Javaにおいてそれらは「文字セット」と呼んで区別されています。
また、Javaの文字列String
や文字シーケンスCharSequence
、文字Char
などの「文字コード」、すなわち内部表現に用いられる文字セットはUTF-16で固定されています。
文字列を他の特定の文字セットに変換したい場合は、以下の記事をご覧ください。