動機
java.regex.Matcher を使って正規表現にマッチした部分文字列を処理する典型的なイディオムは以下である.
Pattern pattern = Pattern.compile("(cola)+");
Matcher matcher = pattern.matcher("cola, cola, I love cola.");
while (mather.find()) {
// do something with the current match.
System.err.println(matcher.group());
}
マッチした箇所に対する read-only な処理をするだけなら,ストリーム処理として書けるべきであろう.こんなふうに.
Matcher matcher = pattern.matcher("cola, cola, I love cola.");
matcher.forEach(h -> System.err.println(h.group()); // h はマッチした箇所を表す何か(定義する必要がある)
Matcherをラップするjava.util.stream.Streamの実装を作れば実現できる,と思ったのは良かったが,このインタフェース,メソッドが多すぎて実装するのが大変である.
https://docs.oracle.com/javase/jp/9/docs/api/java/util/stream/Stream.html
Stream を簡単に実装する方法があるだろうか?
追記: Matcherに対する解決方法
もとの記事では,下にある一般的な解決方法を挙げていたが,Matcherに関してはJava9からMatcher#results()を使うと完全な答えが得られる.このメソッドの実装が,まさに上で書いたやりたいことを実装してくれている.
Pattern pattern = Pattern.compile("(cola)+");
pattern.matcher("cola, cola, I love cola.").results()
.forEach(x -> System.err.println(x.group()));
NOTE: ご指摘下さった @saka1029 さん,ありがとうございました.
なお,Matcher 以外に何かを iterate したい一般的な場合の手法は以下を参照して下さい.
一般的な解決方法
少し調べた結果,StreamSupport#stream と Spliterators#spliteratorUnknownSize を組み合わせることにより,以下のように書けることがわかった.
この方法は,すぐわかるようにMatcherに限らずjava.util.Iteratorの実装に帰着すれば何でも適用可能である.
というわけで,汎用性が高いのでメモしておく次第である.
【追記: この例だけを見れば元のコードより長く複雑になっている,ということは理解しております :-)】
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
*
*/
public class RegExTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
final Pattern pattern = Pattern.compile("(cola)+");
final Stream<Matcher> s = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
new Iterator<Matcher>() {
final Matcher matcher = pattern.matcher("cola, cola, I love cola.");
@Override
public boolean hasNext() {
return matcher.find();
}
@Override
public Matcher next() {
return matcher;
}
},
Spliterator.NONNULL | Spliterator.IMMUTABLE),
false);
// 冒頭の例と同じ使い方ができるようになった!
s.forEach(m -> System.err.println(m.group()));
}
}
追記: java.util.Iteratorを実装せずに済む方法
@saka1029 さんに再びご指摘頂き,java.util.stream.Stream の Java9 から提供されているメソッド [Stream#iterate] (https://docs.oracle.com/javase/jp/10/docs/api/java/util/stream/Stream.html#iterate(T,java.util.function.Predicate,java.util.function.UnaryOperator)) を使うと,以下のように Iterator を実装せずとも綺麗に書けることを知りました.@saka1029 さん,ありがとうございます.Java9 で新しくこの書き方が提供されたということは,こんなふうに書けたらいいのになと思った人が私以外に大勢いたであろうということで,うれしいです.
final Pattern pattern = Pattern.compile("(cola)+");
Stream.iterate(pattern.matcher("cola, cola, I love cola."), m -> m.find(), m -> m)
.forEach(m -> System.out.println(m.group()));