Date July 27, 2013
Tags java / snippet
Share このエントリーをはてなブックマークに追加

文字列をあるパターンによって区切るときはString.split()を使いますが、 そのときに区切り文字そのものも一緒に返してほしい。

単純にString.split()を使うと、区切り文字で区切られた文字列が配列になって返ってきます。 例えば"AとBとC".split("と")は配列["A","B","C"]を返します。

String s = "AとBとC";
String[] ss = s.split("と"); //=> ["A", "B", "C"]

だけど今回は区切り文字も配列内に欲しい。 つまり、"AとBとC".split(ほげほげ)としたときに、["A","と","B","と""C"]にしたいわけです。

結論から先に書くと、先読みと後読みを組み合わせて使います。

String s = "AとBとC";
String pattern = "(?<=と)|(?=と)";
String[] ss = s.split(pattern); //=> ["A","と","B","と","C"]

解説は後にするとして、その他の方法も紹介します。

一つはStringTokenizerを使う方法。StringTokenizerはほぼString.split()と同じ働きをします。 さらにコンストラクタにreturnDelimsというパラメータがあり、 ここがtrueだと区切り文字も配列に含まれて返ってきます。

StringTokenizer st = new StringTokenizer("AとBとC", "と", true);
while(st.hasMoreTokens()){
    System.out.println(st.nextToken());
    //=> A
    //=> と
    //=> B
    //=> と
    //=> C
}

しかしこのStringTokenizer、APIドキュメントではレガシークラスという位置づけで、 新しいコードでの利用は推奨されていません。また、正規表現を区切り文字に使えないという欠点もあります。

実はこの問題はもともとStringTokenizerで書かれていたコードを書きなおそうとしてぶち当たったのです。

次に思いつく方法は、一旦文字列中に表れる区切り文字を絶対使わないような文字で囲んでから、 改めてその使わない文字でsplit()するというものです。

String s = "AとBとC".replaceAll("と", "#と#");
String[] ss = s.split("#"); //=>  ["A","と","B","と","C"]

これなら正規表現も使えますし、何も問題がないように思えます。 実際ほとんどの場合これで間に合うはず。 でもなんとなく気に入りません。スマートじゃないよなあと感じてしまいます。

そもそも、絶対使わないような文字って意外に考えるのが難しいです。 そりゃ最悪ユニコードの制御文字(不可視の区切り文字とか)でもいれとけばいいのでしょうが。

というようなものを調べてきたところで、先に記した先読みと後読みを組み合わせた方法を見つけたのです。

というわけで解説ですが、自分自身あんまり正規表現に関する知識はありません。 もしなにかあったらツッコミお願いします。

さて、この正規表現"((?<=と)|(?=と))"のキモは先読み(?=)と後読み(?<=)を使っているところです。

先読みはパターンがいま見ている文字の先にあるときにマッチします。 つまり、"AとBとC"だったら"A"を読み込んだ時点で"(?=と)"に該当するわけです。 あるいは文字列が"ABCと"だったら、"ABC"と読んだ時点でマッチします。

また、先読みや後読みは一致文字そのものではなく、その位置に反応するみたいです。 そのため、String.split()では一致した区切り文字を配列から除きますが、 この場合"A"及び"と"の間に架空の文字があって、それを取り除いてるように動作します。

さて、"A"を配列に格納した後の文字列は"とBとC"になりますが、この"と"(正確にはその次の位置)を読み込んだ時に (?<=と)にマッチします。先ほどと同じように"と"そのものにマッチしたわけではないので、"と"が配列に格納されます。

これを繰り返すことで、区切り文字そのものを含みつつ、その区切り文字で区切られた配列を得ることができます。

参考サイトではString.format()を用いて、可読性と汎用性を改良したコードが紹介されています。

おそまつさまでした。

参考サイト:
- java - How to split a string, but also keep the delimiters? - Stack Overflow
- 先読みを使ったパターン - 先読みと後読み - Java正規表現の使い方


Comments

comments powered by Disqus