Java Tips | 文字列処理:文字カウント

Java Java
スポンサーリンク

文字カウントは「制限」と「バリデーション」の土台になる

文字カウントは、その名の通り「文字列の長さを数える」処理です。
一見すごく地味ですが、業務システムではかなり重要な役割を持ちます。

入力フォームで「名前は50文字以内」「備考は200文字以内」といった制限をかける。
DBのカラム長(VARCHAR(100) など)を超えないようにチェックする。
メールや外部APIに送る前に、文字数制限を満たしているか確認する。

こういう「制限」「バリデーション」のほとんどは、
どこかで必ず「文字数を数える」処理に行き着きます。

だからこそ、単なる length() で終わらせず、
「何を数えているのか」をちゃんと理解しておくことが大事です。


まずは基本:String#length() で「char の数」を数える

一番シンプルな文字カウントユーティリティ

Javaで一番基本になるのは、String#length() です。
これは「その文字列が何文字か」を返してくれます。

まずは素直にラップしたユーティリティを書いてみます。

public final class CharCounter {

    private CharCounter() {}

    public static int length(String text) {
        if (text == null) {
            return 0;
        }
        return text.length();
    }
}
Java

使い方はこうです。

System.out.println(CharCounter.length("ABC"));      // 3
System.out.println(CharCounter.length("あいう"));   // 3
System.out.println(CharCounter.length("山田太郎")); // 4
System.out.println(CharCounter.length(""));         // 0
System.out.println(CharCounter.length(null));       // 0
Java

ここで最初に押さえておきたい重要ポイントは二つです。

一つ目は、「null をどう扱うかを決めている」ことです。
ここでは「null のときは 0 を返す」という方針にしています。
「null はありえない」と決めて例外を投げる実装もありますが、
業務ユーティリティとしては「0にしておく」と扱いやすい場面が多いです。

二つ目は、「length() が数えているのは“char の数”である」ということです。
ほとんどの日本語や英数字は「1文字=1char」なので、
普段はあまり意識しなくても問題になりません。
ただし、絵文字など一部の文字では話が変わります。
ここが次の重要ポイントです。


「見た目の1文字」と「char の数」は必ずしも一致しない

絵文字や一部の漢字は「2char」で1文字

Javaの char は「UTF-16 の1ユニット」です。
多くの文字は1ユニットで表現できますが、
一部の文字(絵文字、異体字など)は「サロゲートペア」と呼ばれる2ユニットで表現されます。

例えば、こんなコードを見てみます。

String s1 = "A😊B";<br>System.out.println(s1.length()); // 4 になることが多い<br><br>String s2 = "U+20BB7"; // いわゆる「つちよし」の吉<br>System.out.println(s2.length()); // 2 になることが多い<br>
Java

見た目では "A" "😊" "B" の3文字、
"U+20BB7" は1文字ですが、length() はそれぞれ 4 と 2 を返します。

つまり、length() は「見た目の文字数」ではなく、
「UTF-16 のユニット数(char の数)」を返している、ということです。

ここで深掘りしたい重要ポイントは、
「“文字数”という言葉が、何を意味しているかをはっきりさせる必要がある」
ということです。

画面の表示上の「見た目の文字数」を制限したいのか。
DBのカラム長(バイト数)を超えないようにしたいのか。
それとも単に length() の値でざっくりチェックしたいのか。

要件によって、「何を数えるべきか」が変わります。
ここを曖昧にしたまま実装すると、後で必ずズレが出ます。


「見た目の文字数」を数えたいなら codePoint を使う

codePointCount で「Unicodeコードポイントの数」を数える

「絵文字も含めて、見た目の1文字を1カウントにしたい」
という場合は、codePoint 単位で数える必要があります。

Javaには、String#codePointCount(int beginIndex, int endIndex) というメソッドがあります。
これを使うと、「サロゲートペアを1文字として数える」ことができます。

ユーティリティにするとこうなります。

public final class CharCounter {

    private CharCounter() {}

    public static int lengthByCodePoint(String text) {
        if (text == null || text.isEmpty()) {
            return 0;
        }
        return text.codePointCount(0, text.length());
    }
}
Java

使い方はこうです。

System.out.println(CharCounter.lengthByCodePoint("ABC"));      // 3<br>System.out.println(CharCounter.lengthByCodePoint("あいう"));   // 3<br>System.out.println(CharCounter.lengthByCodePoint("A😊B"));     // 3<br>System.out.println(CharCounter.lengthByCodePoint("U+20BB7"));       // 1<br>System.out.println(CharCounter.lengthByCodePoint(""));         // 0<br>System.out.println(CharCounter.lengthByCodePoint(null));       // 0<br>
Java

ここでの重要ポイントは、
length()codePointCount() のどちらを使うかは、要件で決める」
ということです。

「絵文字を含めた見た目の文字数で制限したい」なら codePointCount
「そこまで厳密でなくてよい」「絵文字は想定していない」なら length() でもよい。

大事なのは、「どちらを使っているかを自覚していること」です。
知らないまま使うのが一番危険です。


「文字数制限チェック」ユーティリティにしてみる

length を直接返すだけでなく、「制限を超えているか」を判定する

実務では、「文字数を数える」だけでなく、
「制限以内かどうか」をチェックすることが多いです。

例えば、「名前は50文字以内」というチェックをするユーティリティは、こう書けます。

public final class LengthValidator {

    private LengthValidator() {}

    public static boolean isWithinLength(String text, int maxLength) {
        if (text == null) {
            return true; // null は「未入力」として別途チェックする前提
        }
        return text.length() <= maxLength;
    }

    public static boolean isWithinCodePointLength(String text, int maxLength) {
        if (text == null) {
            return true;
        }
        int len = CharCounter.lengthByCodePoint(text);
        return len <= maxLength;
    }
}
Java

使い方はこうです。

System.out.println(LengthValidator.isWithinLength("山田太郎", 4)); // true
System.out.println(LengthValidator.isWithinLength("山田太郎", 3)); // false

System.out.println(LengthValidator.isWithinCodePointLength("A😊B", 3)); // true
System.out.println(LengthValidator.isWithinCodePointLength("A😊B", 2)); // false
Java

ここでのポイントは二つです。

一つ目は、「null の扱いを“別レイヤー”に分けている」ことです。
ここでは「null は長さチェックOK」として扱い、
「必須入力かどうか」は別のバリデーションで見る、という設計にしています。
これにより、「必須チェック」と「長さチェック」をきれいに分離できます。

二つ目は、「length() 版と codePoint 版を分けて用意している」ことです。
プロジェクトの方針として「絵文字まで考慮するかどうか」を決めたうえで、
どちらか一方を標準として使う、というのが理想です。


「バイト数」を気にする場合は別問題になる

DBのカラム長や外部システムの制限が「バイト数」のとき

ここまでの話は、「文字数」の話でした。
しかし、業務では「バイト数」で制限されることもあります。

例えば、DBのカラムが VARCHAR(100) で、
文字コードが UTF-8 の場合、
日本語1文字が3バイトになることが多いです。

この場合、「100文字以内」ではなく「100バイト以内」をチェックしないと、
DBに入れるときにエラーになる可能性があります。

バイト数を数えるには、getBytes(Charset) を使います。

import java.nio.charset.StandardCharsets;

public final class ByteCounter {

    private ByteCounter() {}

    public static int lengthInUtf8Bytes(String text) {
        if (text == null || text.isEmpty()) {
            return 0;
        }
        return text.getBytes(StandardCharsets.UTF_8).length;
    }
}
Java

使い方はこうです。

System.out.println(ByteCounter.lengthInUtf8Bytes("ABC"));      // 3
System.out.println(ByteCounter.lengthInUtf8Bytes("あ"));        // 3(UTF-8では多くの場合)
System.out.println(ByteCounter.lengthInUtf8Bytes("山田太郎")); // 12 など
Java

ここでの重要ポイントは、
「文字数」と「バイト数」は別物
ということです。

画面の入力制限は「文字数」でよくても、
DBや外部APIは「バイト数」で制限されているかもしれません。

どちらを基準にするかを、要件と設計でちゃんと決めておく必要があります。


実務での文字カウントの使いどころ

例1:入力フォームのサーバサイドバリデーション

例えば、「備考は200文字以内」という入力項目を考えます。

String remark = request.getParameter("remark");

if (remark != null && !LengthValidator.isWithinCodePointLength(remark, 200)) {
    errors.add("備考は200文字以内で入力してください。");
}
Java

ここでは、「見た目の文字数」で制限したい前提で codePoint 版を使っています。
もし「絵文字は想定しない」「lengthで十分」という方針なら、
isWithinLength を使えばOKです。

例2:DBカラム長を超えないようにチェックする

例えば、「ユーザー名はDBのカラムがUTF-8で30バイトまで」という制約があるとします。

String username = request.getParameter("username");

if (username != null && ByteCounter.lengthInUtf8Bytes(username) > 30) {
    errors.add("ユーザー名は30バイト以内で入力してください。");
}
Java

ここでは、「バイト数」を基準にチェックしています。
画面上の見た目とはズレる可能性がありますが、
DB制約を優先する必要がある場面ではこういう書き方になります。


まとめ:文字カウントユーティリティで身につけたい感覚

文字カウントは、「ただ length() を呼ぶだけ」のように見えますが、
その裏にはいくつかの重要な観点があります。

length() が数えているのは「char の数」であること。
絵文字などを「見た目の1文字」として数えたいなら codePointCount を使うこと。
DBや外部システムの制限が「バイト数」の場合は、getBytes で数える必要があること。
null の扱い、必須チェックと長さチェックの分離、といった設計上のルールを決めておくこと。

もしあなたのコードのどこかに、

if (text.length() > 100) { ... }
Java

のような行がそのまま書かれていたら、
それを題材にして、「本当に length() でよいのか」「codePoint かバイト数ではないか」を一度立ち止まって考えてみてください。

その小さな意識の変化が、
「文字列の中身と制約をちゃんと理解して扱えるエンジニア」への、確かな一歩になります。

タイトルとURLをコピーしました