Windows-31J でエンコードしたときのバイト数を基準として全角・半角を判定する方法を紹介します。エンコードできない場合は半角でも全角でもないものとします。
実装
以下のメソッドは、入力が半角文字だと1、全角文字だと2を返し、どちらでもない場合には例外 CharacterCodingException を投げます。
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
public class Windows31JWidthChecker {
private static final Charset WINDOWS31J = Charset.forName("windows-31j");
/**
* Windows-31J での文字幅を返す。
* @return 半角文字は 1、全角文字は 2。
* @throws CharacterCodingException Windows-31J で表せない文字だった場合。
*/
public static int widthInWindows31J(char c) throws CharacterCodingException {
CharBuffer charBuffer = CharBuffer.allocate(1).put(c);
charBuffer.flip();
ByteBuffer byteBuffer = WINDOWS31J.newEncoder().encode(charBuffer);
return byteBuffer.limit();
}
}
動作確認
以下のコードで動作確認してみます。
import java.nio.charset.CharacterCodingException;
class Windows31JWidthCheckerTest {
public static void main(String[] args) {
char[] chars = {
'a', 'a', 'β', '○', 'カ', 'が', 'か', '゙',
'茶', '茶', '丳', '㍍', '㌀', 'क', ''
};
for (char c : chars) {
String result;
try {
int width = Windows31JWidthChecker.widthInWindows31J(c);
if (width == 1) {
result = "半角";
} else if (width == 2) {
result = "全角";
} else {
throw new IllegalStateException("logic error!");
}
} catch (CharacterCodingException e) {
result = "どちらでもない";
}
System.out.printf("「%s」(U+%04X) は%s%n", c, (int) c, result);
}
}
}
実行結果は次のようになります。
「a」(U+0061) は半角
「a」(U+FF41) は全角
「β」(U+03B2) は全角
「○」(U+25CB) は全角
「カ」(U+FF76) は半角
「が」(U+304C) は全角
「か」(U+304B) は全角
「゙」(U+3099) はどちらでもない
「茶」(U+8336) は全角
「茶」(U+F9FE) はどちらでもない
「丳」(U+4E33) はどちらでもない
「㍍」(U+334D) は全角
「㌀」(U+3300) はどちらでもない
「क」(U+0915) はどちらでもない
「」(U+E000) は全角
うまく動作していますね。
U+E000 が全角と判定されるのは気持ち悪い感じもしますが、Unicode の私用領域のうち、U+E000 などはシフト JIS のユーザー外字領域にマッピングするものなので、これが期待通りの結果です。
String.getBytes("windows-31j") でバイト数を数えたほうが簡単じゃない?
これは良くない方法だと思います。変換できない文字が "?" に置き換えられてしまうため、誤って半角と判定してしまいます。例えば「㌀」('\u3300') の幅を1 (半角) と誤判定してしまいます。
jshell> "\u3300".getBytes("windows-31j").length
$1 ==> 1
CharsetEncoder を用いると、デフォルトでこうした場合にエラーを報告してくれます。
サロゲート ペアに対応しなくて平気?
CP932 は Windows-31J の別名ですが、下記変換表を見ると char で表現できる U+FFFF 以下の文字との対応関係として定義されているので、特別な対応は必要ありません。サロゲート ペアになる場合には「どちらでもない」という結果になります。
念のため以下のように、サロゲート レンジの char で必ず「どちらでもない」を表す例外が出ることも確認しました。
import org.junit.jupiter.api.Test;
import java.nio.charset.CharacterCodingException;
import static org.junit.jupiter.api.Assertions.assertThrows;
class SecondWindows31JWidthCheckerTest {
@Test
void shouldThrowExceptionForSurrogate() throws CharacterCodingException {
for (char c = '\ud800'; c <= '\udfff'; c++) {
char finalC = c;
assertThrows(CharacterCodingException.class, () -> Windows31JWidthChecker.widthInWindows31J(finalC));
}
}
}