SafeSubstring は「落ちない部分文字列」を返すための小さな盾
String#substring は便利ですが、そのまま使うとすぐに例外を投げます。beginIndex や endIndex が範囲外なら StringIndexOutOfBoundsException。null に対して呼べば NullPointerException。
業務コードのあちこちで if (str != null && str.length() >= ...) のようなガードを書いていると、
読みづらいし、抜け漏れも起きやすい。
そこで、「インデックスが多少おかしくても“安全な範囲に丸めて”部分文字列を返す」
小さなユーティリティを用意しておくと、コードがかなりスッキリします。
それがここでいう SafeSubstring です。
基本形:インデックスを「安全な範囲」に丸めてから substring する
例外を投げずに「できる範囲で切り出す」
まずは、start と end を受け取って、
範囲外なら自動で補正してから substring する基本版を作ってみます。
public final class SafeSubstring {
private SafeSubstring() {}
public static String safeSubstring(String text, int start, int end) {
if (text == null || text.isEmpty()) {
return "";
}
int len = text.length();
if (start < 0) {
start = 0;
}
if (end > len) {
end = len;
}
if (start >= end) {
return "";
}
return text.substring(start, end);
}
}
Java使い方のイメージはこうです。
String s = "ABCDEFG";
System.out.println(SafeSubstring.safeSubstring(s, 0, 3)); // ABC
System.out.println(SafeSubstring.safeSubstring(s, -5, 3)); // ABC(start が 0 に丸められる)
System.out.println(SafeSubstring.safeSubstring(s, 3, 100)); // DEFG(end が length に丸められる)
System.out.println(SafeSubstring.safeSubstring(s, 10, 20)); // ""(空文字)
System.out.println(SafeSubstring.safeSubstring(null, 0, 3)); // ""
Javaここで深掘りしたい重要ポイントは三つあります。
一つ目は、「null や空文字のときは空文字を返す」と決めていることです。
これにより、呼び出し側は safeSubstring の結果をそのまま連結や比較に使えます。
二つ目は、「start と end を“安全な範囲”に丸めている」ことです。
負の start は 0 に、長すぎる end は length に。
これで、範囲外アクセスによる例外を防げます。
三つ目は、「start >= end のときは空文字を返す」というルールです。
この条件を一箇所に閉じ込めておくことで、
呼び出し側は「インデックスの大小関係」をいちいち気にしなくて済みます。
「先頭から最大 N 文字だけ欲しい」版を用意する
よくある「左側切り出し」を安全に書く
業務でよくあるのが、「先頭から最大 N 文字だけ切り出したい」というパターンです。
これも SafeSubstring にまとめてしまいましょう。
public static String left(String text, int maxLength) {
if (text == null || text.isEmpty() || maxLength <= 0) {
return "";
}
int len = text.length();
if (maxLength >= len) {
return text;
}
return text.substring(0, maxLength);
}
Java使い方はこうなります。
String s = "ABCDEFG";
System.out.println(SafeSubstring.left(s, 3)); // ABC
System.out.println(SafeSubstring.left(s, 100)); // ABCDEFG
System.out.println(SafeSubstring.left(s, 0)); // ""
System.out.println(SafeSubstring.left(null, 3)); // ""
Javaここでのポイントは、「maxLength が 0 以下なら空文字」「文字列より長ければそのまま返す」という、
直感に合うルールを一箇所に固定していることです。
これを毎回 if 文で書くのではなく、
「左側切り出しは必ず SafeSubstring.left を通す」と決めておくと、
コードの見通しがかなり良くなります。
「末尾から最大 N 文字だけ欲しい」版もよく使う
右側切り出しも安全に
ログのIDやファイル名の末尾だけを見たい、
といった場面では「右側から N 文字」が欲しくなります。
public static String right(String text, int maxLength) {
if (text == null || text.isEmpty() || maxLength <= 0) {
return "";
}
int len = text.length();
if (maxLength >= len) {
return text;
}
int start = len - maxLength;
return text.substring(start);
}
Java使い方は次の通りです。
String s = "ABCDEFG";
System.out.println(SafeSubstring.right(s, 3)); // EFG
System.out.println(SafeSubstring.right(s, 100)); // ABCDEFG
System.out.println(SafeSubstring.right(s, 0)); // ""
System.out.println(SafeSubstring.right(null, 3)); // ""
Javaここでも、「範囲外にならないように start を計算する」ロジックを
ユーティリティに閉じ込めているのがポイントです。
絵文字・サロゲートペアを意識した「コードポイント版 SafeSubstring」
「見た目の1文字」で切りたいときは length では足りない
ここまでの実装は、String#length() と substring をそのまま使っています。
これは「UTF-16 の char 単位」での切り出しです。
しかし、絵文字や一部の漢字(サロゲートペア)は 2char で1文字なので、
「見た目の N 文字で切りたい」場合にはズレが出ます。
「見た目の文字数」で安全に切りたい場合は、コードポイント単位で扱う必要があります。
public static String safeSubstringByCodePoint(String text, int startCp, int endCp) {
if (text == null || text.isEmpty()) {
return "";
}
int cpCount = text.codePointCount(0, text.length());
if (startCp < 0) {
startCp = 0;
}
if (endCp > cpCount) {
endCp = cpCount;
}
if (startCp >= endCp) {
return "";
}
int startIndex = text.offsetByCodePoints(0, startCp);
int endIndex = text.offsetByCodePoints(0, endCp);
return text.substring(startIndex, endIndex);
}
Java使い方の例です。
String s = "A😊B"; // 見た目は3文字だが length() は4
System.out.println(SafeSubstring.safeSubstring(s, 0, 2)); // "A�" など崩れる可能性
System.out.println(SafeSubstring.safeSubstringByCodePoint(s, 0, 2)); // "A😊"
Javaここでの重要ポイントは、「length ではなく codePointCount と offsetByCodePoints を使っている」ことです。
これにより、「見た目の1文字」を単位として安全に切り出せます。
業務で絵文字を扱うかどうか、どこまで厳密にやるかは要件次第ですが、
「char 単位の SafeSubstring」と「コードポイント単位の SafeSubstring」を分けて持っておくと、
後から要件が変わったときにも対応しやすくなります。
まとめ:SafeSubstring で本当に守りたいもの
SafeSubstring は、「substring の例外やインデックス計算ミスから自分を守るための小さな盾」です。
null や空文字のときは空文字を返す。
start と end を安全な範囲に丸める。
start >= end のときは空文字にする。
左側・右側切り出しのよくあるパターンをユーティリティに閉じ込める。
必要に応じて、コードポイント版(見た目の文字数版)も用意する。
もしあなたのコードのどこかに、
str.substring(0, 10)
str.substring(str.length() - 5)
Javaのような生の substring が散らばっていたら、
それを一度 SafeSubstring に置き換えてみてください。
その小さなリファクタリングが、
「例外に怯えず、意図のはっきりした文字列処理を書けるエンジニア」への、
確かな一歩になります。
