Java | Java 標準ライブラリ:split の落とし穴

Java Java
スポンサーリンク

split は「正規表現で区切る」メソッドだという前提

String#split は、 「文字列を区切り文字で分割する」メソッド ですが、
まず一番大事なポイントは

「引数は“ただの区切り文字”ではなく、“正規表現”として解釈される」

ということです。

ここを意識していないと、

なぜか変なところで分割される
.| で split したら想定外の結果になる
末尾の空要素が消えてしまう

といった「落とし穴」にハマります。

ここから、よくある落とし穴を順番にかみ砕いていきます。


落とし穴1:引数が「正規表現」だという事実を忘れる

ドットやパイプで split するとおかしくなる理由

例えば「ピリオドで区切りたい」場合、ついこう書きがちです。

String s = "a.b.c";
String[] parts = s.split(".");
Java

一見よさそうですが、これは 大事故 です。
なぜなら、正規表現において .

「何か1文字」

という意味だからです。

つまり split(".") は「どんな1文字でも区切り」と解釈されます。
結果、ほぼすべての文字で分割されてしまいます。

正しくは「. を“ただのドット”として扱う」ために、正規表現の世界でエスケープが必要です。

String s = "a.b.c";
String[] parts = s.split("\\.");
// 結果: ["a", "b", "c"]
Java

ここで \\. となっているのは、

正規表現としては \. と書きたい
でも Java の文字列リテラルでは \ 自体もエスケープが必要

なので、コード上は \\. になる、という二重エスケープのせいです。

同じく、| も正規表現では「OR(または)」を意味します。

String s = "A|B|C";
String[] parts = s.split("|");   // これも事故
Java

split("|") は「文字と文字の間が全部区切り」となるので、
一文字ずつバラバラになります。

これも \| としてエスケープすべきです。

String[] parts = s.split("\\|");
Java

対策の基本方針

「split の引数は正規表現」という前提に立って、

.|, *, +, ?, (, ), [, ], {, }, ^, $ など
正規表現で意味を持つ文字を区切りに使いたいときは必ずエスケープする

という意識を持っておくと安全です。


落とし穴2:末尾の空文字が勝手に捨てられる仕様

想定外に配列のサイズが小さくなるパターン

次に厄介なのが 「末尾の空要素が削られてしまう」 という仕様です。

例えば CSV を簡易的に split するとして、こういう文字列があったとします。

String s = "a,b,";
String[] parts = s.split(",");
System.out.println(parts.length);      // いくつだと思う?
System.out.println(Arrays.toString(parts));
Java

直感的には ["a", "b", ""] の3要素を期待するかもしれません。
ところが実際は

2
[a, b]

になります。

理由は、split(String regex) のデフォルト仕様が

「末尾の空文字列は捨てる」

だからです。

つまり a,b,, で分割すると、
内部的には ["a", "b", ""] が生成されるのですが、
最後の "" が削除されて ["a", "b"] だけが返ってきます。

limit を指定すると動きが変わる(重要)

末尾の空要素も含めて全部ほしい場合は、
第二引数に limit を指定します。

String s = "a,b,";
String[] parts = s.split(",", -1);  // limit に負数
System.out.println(parts.length);   // 3
System.out.println(Arrays.toString(parts));  // [a, b, ""]
Java

split(regex, limit)limit には以下のルールがあります。

limit > 0
最大 limit - 1 回だけ分割する。末尾空要素は捨てない(場合がある)。

limit == 0
「デフォルト」と同じ扱い。末尾空要素を削る。

limit < 0
回数無制限で分割し、末尾空要素も含めて全部返す。

実務でよく使うのは、

「末尾の空要素も含めて、すべて取りたい」 → split(regex, -1)

です。

特に CSV やログ解析など、「フィールド数が固定なのに、最後の項目だけ空」というケースでは、
split(regex) だけだと列数が合わなくなってバグの原因になります。


落とし穴3:空文字を含むケースと、区切り文字が連続するケース

区切り文字が続くと「空文字」が入る

例えば、カンマ区切りの値で空フィールドが途中にあるケース。

String s = "a,,c";
String[] parts = s.split(",", -1);
System.out.println(parts.length);           // 3
System.out.println(Arrays.toString(parts)); // [a, "", c]
Java

a,,c の真ん中は「空文字」として扱われます。
これは仕様どおりですが、空文字をまともに考慮していないと、

null と空文字を混同してしまう
空のフィールドが飛ばされたと勘違いする

といった混乱を招きがちです。

「空文字列を split するとどうなるか」

これもよく聞かれます。

String s = "";
String[] parts1 = s.split(",", -1);
String[] parts2 = s.split(",");
Java

結果はこうなります。

split(",", -1)[""](1 要素、空文字)
split(",")[](0 要素の配列)

デフォルト( limit = 0 )だと「末尾の空要素は削る」ので、
空文字列は「末尾の空要素1つだけ」→ それも削られて要素 0 になります。

これも頭の片隅に置いておくと、「あれ?配列が空なんだけど?」というときの理解が早くなります。


落とし穴4:「簡易 CSV パーサとして使う」と地雷を踏みがち

split(",") で CSV を分解するのは危険

初心者がとてもやりがちなのが、

String line = "a,b,c";
String[] fields = line.split(",");
Java

というノリで、「CSV をすべて split で処理しようとする」ことです。

しかし CSV には、次のようなやっかいな仕様があります。

フィールド内にカンマを含めるために "a,b" のようにダブルクォートで囲む
ダブルクォート自体を含めるために "" でエスケープする

例えばこういう行です。

"山田,太郎",25,"東京都,港区"

これを split(",") すると、
ダブルクォート内のカンマまで区切りとみなされて爆散します。

CSV は一見「カンマ区切りだから簡単」と思われがちですが、
正しく処理しようとすると意外と複雑です。

実務では、素直に既存の CSV ライブラリ(OpenCSV など)を使うほうが安全です。
split はあくまで「シンプルな区切りや、軽いテキスト処理」に使うもの、と捉えておくとよいです。


落とし穴5:パフォーマンスと「やりすぎ正規表現」

単純な 1 文字区切りに毎回複雑な正規表現を書かない

例えば、スペースかタブの連続で区切りたいので

String[] parts = s.split("\\s+");
Java

のように書くのは、むしろ正しい使い方です。
一方で、単に "," で区切りたいだけなのに、

String[] parts = s.split("\\,");
Java

のように、なんとなく毎回正規表現を意識して「過剰に」書いてしまうのも、
読みやすさを落とす原因になります。

さらに、超複雑な正規表現で split を繰り返すと、
実行速度に影響することもあります(特に巨大な文字列、回数の多いループの中で)。

初心者の段階では、

本当にシンプルな 1 文字区切り → 場合によっては indexOf / substring なども検討
空白や複数種類の区切り記号をまとめて扱いたい → 正規表現で split は有効

くらいの感覚で、「何でもかんでも split(regex)」にせず、
用途に合わせて手段を選ぶ意識を持つとよいです。


まとめ:split の「正しい付き合い方」を頭にインストールする

split の落とし穴を整理すると、次のようなポイントに集約されます。

引数は「正規表現」。.| などのメタ文字は必ずエスケープが必要。
デフォルトの split(regex) は「末尾の空要素を捨てる」仕様。全部ほしいなら split(regex, -1)
区切り文字の連続や、完全な空文字列に対する挙動(空文字が要素として入るか / 入らないか)を意識する。
CSV など複雑なフォーマットに split をそのまま使うのは危険。専用ライブラリを検討。

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