区切り文字を含めて文字列分割する


はじめに

以下のような文字列を : で分割する。

var string = "aa:bb::cc";

split を使うと : で分割され、分割文字である : は含まれない。

string.split(":");

// Arrays.deepToString(string.split(":"));
// -> [aa, bb, , cc]


ゼロ幅分割

改行文字で分割し、分割後には改行文字を残したいようなケースでは、ゼロ幅マッチ(zero-length match) の先読み(lookahead)を使う。

string.split("(?<=:)");
// -> [aa:, bb:, :, cc]

先読みではなく、戻り読み(lookbehind) を使うと以下のようになる。

string.split("(?=:)");
// -> [aa, :bb, :, :cc]

先読み or 戻り読み のマッチで分割すると、以下のように区切り文字自身を含めて分割できる。

string.split("((?<=:)|(?=:))");
// -> [aa, :, bb, :, :, cc]


ゼロ幅 (zero-length) とは

正規表現の JavaDoc には以下のように説明されるが、この説明で理解するのは難しい。

正規表現 説明 かみ砕くと
(?=pattern) 幅ゼロの肯定先読み 直後にpatternがある境界
(?<=pattern) 幅ゼロの肯定戻り読み 直前にpatternがある境界
(?!pattern) 幅ゼロの否定先読み 直後にpatternがない境界
(?<!pattern) 幅ゼロの否定戻り読み 直前にpatternがない境界

ゼロ幅 とは、文字の境界といった、位置にマッチするもので、^ (行頭) $ (行末) \b (単語の境界) などもゼロ幅マッチとなる。

ゼロ幅アサーション(zero-length assertion)とも呼ばれ、長さ0の文字列にマッチするものを意味する。

先ほどの、split() による分割位置の指定では以下のように分割境界が指定されることとなる。


JDK21 の String.splitWithDelimiters()

JDK21 では String.splitWithDelimiters() が追加され、((?<=:)|(?=:)) のケースは正規表現を書かずに実現できるようになった。

string.splitWithDelimiters(":", -1);  // -> [aa, :, bb, :, , :, cc]