文字列分割は「一つの塊を意味ごとに切り出す」技
業務システムでは、CSVの1行、ログ1行、設定ファイルの1行、HTTPヘッダ、クエリ文字列など、「一つの長い文字列を、意味のあるピースに切り分けたい」場面がひたすら出てきます。
ここをなんとなく split で書き始めると、「思った場所で切れない」「空文字が混ざる」「末尾の空要素が消える」「パフォーマンスが落ちる」といった、地味だけど厄介な問題にぶつかります。
だからこそ、「業務でよく使う分割パターンを、ユーティリティとして“型”にしておく」ことが大事になります。
今日はそのための考え方と、初心者でもすぐ使えるコードパターンを、丁寧にかみ砕いていきます。
Java の基本武器 String.split の正体をちゃんと知る
String.split は、Java で一番よく使われる分割メソッドです。
String s = "apple,banana,grape";
String[] parts = s.split(",");
System.out.println(Arrays.toString(parts)); // [apple, banana, grape]
Javaここまでは直感どおりですが、重要なのは「split の引数は“区切り文字”ではなく“正規表現”として解釈される」という点です。
例えば、次のコードを見てください。
String s = "a.b.c";
String[] result = s.split(".");
System.out.println(Arrays.toString(result));
Java「. で区切るから [“a”, “b”, “c”] かな」と思いきや、実際には空配列になってしまいます。
理由は、正規表現において . は「任意の1文字」を意味する特別な記号だからです。
このように、「.」「|」「*」「+」「?」「[」「]」など、正規表現で意味を持つ文字を区切りに使いたいときは、そのまま split に渡すと壊れます。
ここを安全にするための“お作法”を、ユーティリティに閉じ込めてしまうのが賢いやり方です。
正規表現を意識しなくて済む split ユーティリティを作る
区切り文字列を「正規表現ではなくリテラルとして扱う」
「. や | を含む区切りでも安全に分割したい」「呼び出し側に正規表現を意識させたくない」というときに使えるのが Pattern.quote です。
import java.util.regex.Pattern;
public final class StringSplitter {
private StringSplitter() {}
public static String[] splitLiteral(String text, String delimiter) {
if (text == null) {
return new String[0];
}
if (delimiter == null || delimiter.isEmpty()) {
return new String[] { text };
}
String regex = Pattern.quote(delimiter);
return text.split(regex, -1); // -1 は末尾の空要素も残す指定
}
}
Javaここで深掘りしたい重要ポイントは二つあります。
一つ目は、「Pattern.quote で区切り文字列を“正規表現としてではなく、ただの文字列として扱う”ようにしている」ことです。
これにより、. や | を含む区切りでも、安心して splitLiteral に渡せます。
二つ目は、「split の第二引数に -1 を渡している」ことです。String.split は、デフォルトだと末尾の空文字列を削ってしまう挙動がありますが、-1 を指定すると末尾の空要素もそのまま残してくれます。
CSV やログなどで「最後のカラムが空かどうか」が意味を持つ場合、この違いはかなり重要です。
例題1:CSV風の1行を安全に分割する
「カンマ区切りの1行を配列にしたい」というのは、業務で最もよく出てくるパターンの一つです。
ここでは、あえて「ダブルクォートによるエスケープは考えない、シンプルな CSV 風」と割り切って、splitLiteral を使ってみます。
public final class CsvLineParser {
private CsvLineParser() {}
public static String[] parseSimpleCsvLine(String line) {
return StringSplitter.splitLiteral(line, ",");
}
}
Java使い方はこうなります。
String line = "山田太郎,30,東京,";
String[] cols = CsvLineParser.parseSimpleCsvLine(line);
System.out.println(cols.length); // 4
System.out.println(cols[0]); // 山田太郎
System.out.println("\"" + cols[3] + "\""); // ""(空文字)
Javaここでのポイントは、「末尾のカンマの後ろの“空のカラム”も、ちゃんと配列に残っている」ことです。split のデフォルト挙動だと、この最後の空要素が消えてしまい、「カラム数が合わない」というバグの原因になります。
もちろん、実際の CSV ではダブルクォートや改行など、もっと複雑なルールがありますが、
「まずはシンプルなケースを安全に扱えるユーティリティを持つ」というのは、十分に実務的な一歩です。
例題2:1文字区切り専用の高速 split を自作する
String.split は便利ですが、「正規表現エンジンを使う」という性質上、頻繁に呼ぶとオーバーヘッドが気になることがあります。
「区切りは1文字で十分」「とにかく速くたくさん分割したい」という場合は、自前でループを書くのも有効です。
import java.util.ArrayList;
import java.util.List;
public final class FastCharSplitter {
private FastCharSplitter() {}
public static String[] splitByChar(String text, char delimiter) {
if (text == null) {
return new String[0];
}
List<String> result = new ArrayList<>();
int start = 0;
int len = text.length();
for (int i = 0; i < len; i++) {
if (text.charAt(i) == delimiter) {
result.add(text.substring(start, i));
start = i + 1;
}
}
result.add(text.substring(start, len)); // 最後の要素
return result.toArray(new String[0]);
}
}
Java使い方はこうです。
String s = "A|B||D";
String[] parts = FastCharSplitter.splitByChar(s, '|');
System.out.println(Arrays.toString(parts)); // [A, B, , D]
Javaここで深掘りしたいのは、「String.split のように正規表現を解釈しないので、. や | などもそのまま区切りとして扱える」ことと、
「正規表現エンジンを使わないぶん、単純なループとして非常に速い」ということです。
業務で大量のログやテキストを処理するとき、「ここは1文字区切りで十分」と分かっている箇所にこうしたユーティリティを使うと、
パフォーマンスと挙動の安定性を両立できます。
例題3:トリムや空要素除去まで含めた「業務用 split」
実務では、「分割したあとに前後の空白を削りたい」「空文字の要素はいらない」といった要件もよく出てきます。
これも毎回呼び出し側で書くのではなく、ユーティリティに閉じ込めてしまいましょう。
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public final class BusinessStringSplitter {
private BusinessStringSplitter() {}
public static String[] splitAndTrimIgnoreEmpty(String text, String delimiter) {
if (text == null) {
return new String[0];
}
String regex = Pattern.quote(delimiter);
String[] raw = text.split(regex, -1);
List<String> result = new ArrayList<>();
for (String r : raw) {
if (r == null) {
continue;
}
String trimmed = r.trim();
if (!trimmed.isEmpty()) {
result.add(trimmed);
}
}
return result.toArray(new String[0]);
}
}
Java使い方はこうです。
String s = " 山田 , , 佐藤 ,鈴木 ";
String[] names = BusinessStringSplitter.splitAndTrimIgnoreEmpty(s, ",");
System.out.println(Arrays.toString(names)); // [山田, 佐藤, 鈴木]
Javaここでの重要ポイントは、「“どこまでを分割の責務とするか”をユーティリティ側で決めている」ことです。
このメソッドを使うと、「区切りで分割する」「前後の空白を削る」「空要素を捨てる」という三つの処理を、
呼び出し側は一行で済ませられます。
こうして「業務でよく出るパターン」を名前付きメソッドにしていくと、
コードを読むときに「何をしたいのか」が一瞬で分かるようになり、バグも入り込みにくくなります。
パフォーマンスと設計をどうバランスさせるか
文字列分割は、ログ解析や大量データ処理ではボトルネックになりやすい一方で、
小さな文字列に対してたまに呼ぶ程度なら、String.split のオーバーヘッドはほとんど気になりません。
初心者向けに整理すると、次のような感覚で十分です。
小規模・たまにしか呼ばない処理なら、まずは splitLiteral や splitAndTrimIgnoreEmpty のような「読みやすいユーティリティ」を優先する。
大量データ・高頻度の処理でボトルネックになってきたら、FastCharSplitter のような「用途特化の高速版」を導入する。
そして何より大事なのは、「split を生であちこちに書かず、“分割のルール”をユーティリティに集約する」という設計の姿勢です。
そうしておけば、「末尾の空要素を残したい」「空白を削りたい」「区切りが正規表現と衝突する」といった仕様変更があっても、
ユーティリティを直すだけで、呼び出し側はほぼノータッチで済むようになります。
まとめ:文字列分割ユーティリティで身につけたい感覚
文字列分割は、「一つの塊を意味ごとに切り出す」ための、とても地味だけれど超重要な技です。
押さえておきたいのは、まず「String.split の引数は正規表現であり、. や | などはそのまま渡すと壊れる」という事実。
次に、「Pattern.quote や自前ループを使って、“正規表現を意識しなくていい分割”をユーティリティとして用意する」こと。
そして、「トリム・空要素除去・末尾の空要素保持など、“業務で必要なルール”を分割ユーティリティに閉じ込めて、プロジェクト全体で同じ挙動を共有する」ことです。
もしあなたのコードのどこかに、s.split(",") や s.split("\\|") が生で散らばっているなら、
その一つを題材にして、ここで作った StringSplitter.splitLiteral や BusinessStringSplitter.splitAndTrimIgnoreEmpty に置き換えてみてください。
それだけで、「壊れにくくて、読みやすくて、パフォーマンスもコントロールしやすい文字列分割」に、一段レベルアップできます。
