6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Java で全角・半角・どちらでもないを判定する方法

Last updated at Posted at 2022-09-29

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));
        }
    }
}
6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?