No Programming, No Life

新しいNPNLです。http://d.hatena.ne.jp/fumokmm/ から引っ越してきました。

GroovyなJDK、それがGDK(String編その3)


このシリーズの一覧はこちら

はじめに

GroovyにはJavaの標準API(JDK)を拡張したGroovy JDK(GDK)があります。大量の便利メソッドが追加されており、これを使いこなすだけでも相当のことができるようになります。このシリーズでは毎回1クラスずつ各メソッドの使用例をサンプル付きでご紹介していきたいと思います。
「今回は java.lang.String です」


String編その3では検索系と置換系のメソッドをご紹介します。それでは早速見て行きましょう。

int size() [since v1.0]

文字列の長さを取得します。
Groovyではさまざまなオブジェクトに#size()を追加してあるため
lengthは使うことは滅多にありません。
思想としてはさまざまなオブジェクトで統一的に長さ取得は#size()で行えるようにしてポリモーフィズムを実現しようというもの。

assert 'あいうえお'.size() == 5
assert 'abc'.size() == 3
assert ''.size() == 0

int count(String text) [since v1.0]

文字列中に指定文字がいくつあるか数えてくれます。

assert 'abc'.count('b') == 1
assert 'aaaaa'.count('aa') == 4 // 一つずつindexをずらしてちゃんと照合してくれてるみたい
assert 'ディスコディスコワンルームディスコ'.count('ディスコ') == 3 // 全角もOK

boolean contains(String text) [since v1.0]

これは標準JDKJava5から導入された#contains(CharSequence s)と同様の機能です。
思想としてはCollection#containsと同じ機能をStringにも実装して、ポリモーフィズムを実現しようというもの。

assert 'abc'.contains('a')
'abc'.contains(null) // => java.lang.NullPointerException

boolean matches(Pattern pattern) [since v1.6.1]

指定されたPatternにこの文字列がマッチするかテストします。

assert 'abc'.matches(~/[abc]{3}/)

String eachMatch(Pattern pattern, Closure closure) [since v1.6.1]

String eachMatch(String regex, Closure closure)参照

String eachMatch(String regex, Closure closure) [since v1.6.0]

第1引数に指定した正規表現(文字列でもPatternでもOK)についてヒットした部分を第2引数のクロージャに渡します。

'aaa123bbb456ccc'.eachMatch(/[a-z]+/){ // ~/[a-z]+/ でもOK (java.util.regex.Pattern)
  println it
}
出力
aaa
bbb
ccc

また、正規表現にグループが指定されていた場合、クロージャに渡すパラメータを増やすことで順番に受け取れます。
クロージャの最初のパラメータは正規表現がヒットした全体になります。

'123-4567'.eachMatch(/(\d{3})-(\d{4})/){ all, z1, z2 ->
  println "$z1, $z2, 正規表現がヒットした全体 -> [$all]"
}
出力
123, 4567, 正規表現がヒットした全体 -> [123-4567]

String find(String regex) [since v1.6.1]

文字列を正規表現(文字列でもPatternでもOK)で検索して最初にヒットした部分を返却します。

assert 'aaa123bbb456ccc'.find(/[a-z]+/) == 'aaa'
assert 'aaa123bbb456ccc'.find(~/[a-z]+/) == 'aaa'

ヒットしなかった場合はnullが返却されます。

assert 'aaa123bbb456ccc'.find(/[A-Z]+/) == null
assert 'aaa123bbb456ccc'.find(~/[A-Z]+/) == null

String find(Pattern pattern) [since v1.6.1]

String find(String regex)参照

String find(String regex, Closure closure) [since v1.6.1]

第1引数に指定した正規表現(文字列でもPatternでもOK)についてヒットした部分を第2引数のクロージャに渡します。
findAllでないため、1回しかヒットしない。そのためクロージャ内のコードは1回しか実行されません。

'aaa123bbb456ccc'.find(/[a-z]+/){ assert it == 'aaa' }
'aaa123bbb456ccc'.find(~/[a-z]+/){ assert it == 'aaa' }

ヒットしなかった場合はクロージャが呼び出されません。

println 'start'
'aaa123bbb456ccc'.find(/[A-Z]+/){ println '実行されるかな?(String)' }
'aaa123bbb456ccc'.find(~/[A-Z]+/){ println '実行されるかな?(Pattern)' }
println 'end'
出力
start
end

このメソッド自体の戻り値はクロージャ内で最後に評価された値がtoString()された値となります。*1

def result = 'aaa123bbb456ccc'.find(/[a-z]+/){ it }
assert result == 'aaa'

これは String#find(regex) は String.find(regex){ it } で同じようなことができます。

最後に評価された値が戻り値となるサンプルはこのような感じです。

def result = 'aaa123bbb456ccc'.find(/[a-z]+/){ it; 123 } // 結果をIntegerにしてしまうと・・・
assert result == '123'
assert result.class == java.lang.String // Stringに強制型変換される

正規表現でヒットさせておいて、その結果を受け取った処理をクロージャ内に書きたい場合に有効な手法ということのようです。

String find(Pattern pattern, Closure closure) [since v1.6.1]

String find(String regex, Closure closure)参照

List findAll(String regex) [since v1.6.1]

第1引数に指定した正規表現(文字列でもPatternでもOK)についてヒットした部分をリストにして返却します、
String#findは最初にヒットした部分しか返してくれませんでしたが、こちらを利用すれば全ての要素をリストで取得できるため、使い勝手がとてもよいです。

assert 'aaa123bbb456ccc'.findAll(/[a-z]+/) == ['aaa', 'bbb', 'ccc']
assert 'aaa123bbb456ccc'.findAll(~/[a-z]+/) == ['aaa', 'bbb', 'ccc']

つまり String#find(regex) は String#findAll(regex).first() と同じ。

ヒットしなかった場合は空リストが返却されます。

assert 'aaa123bbb456ccc'.findAll(/[A-Z]+/) == []
assert 'aaa123bbb456ccc'.findAll(~/[A-Z]+/) == []

List findAll(Pattern pattern) [since v1.6.1]

List findAll(String regex)参照

List findAll(String regex, Closure closure) [since v1.6.1]

第1引数に指定した正規表現(文字列でもPatternでもOK)についてヒットした部分すべてについて第2引数のクロージャに渡し、その結果のリストを返却します。
結果を加工できるという点から、List#collectに近いイメージかもしれません。
つまり、String#findAll(regex).collect{ ... } で同じようなことができます。

def resultList = 'aaa123bbb456ccc'.findAll(/[a-z]+/){ // ~/[a-z]+/ でもOK (java.util.regex.Pattern)
  println it
  "<$it>" // <>を付けて加工してみる
}
assert resultList == ['<aaa>', '<bbb>', '<ccc>']
出力
aaa
bbb
ccc

また、正規表現にグループが指定されていた場合、クロージャに渡すパラメータを増やすことで順番に受け取れます。
クロージャの最初のパラメータは正規表現がヒットした全体になります。

def zipCodeList = '123-4567 987-6543 000-0000'.findAll(/(\d{3})-(\d{4})/){ all, z1, z2 ->
  println "$z1, $z2, 正規表現がヒットした全体 -> [$all]"
  "〒${z1}${z2}" // 郵便番号表記に加工してみる
}
assert zipCodeList == ['〒123−4567', '〒987−6543', '〒000−0000']
出力
123, 4567, 正規表現がヒットした全体 -> [123-4567]
987, 6543, 正規表現がヒットした全体 -> [987-6543]
000, 0000, 正規表現がヒットした全体 -> [000-0000]

List findAll(Pattern pattern, Closure closure) [since v1.6.1]

List findAll(String regex, Closure closure)参照

String tr(String sourceSet, String replacementSet) [since v1.7.3]

二つの文字列を受け取って、対応するindexの位置の文字列で置き換えてゆきます。*nix系OSのtrコマンドと同じですね。

// すべての 'a' を 'A' に変換
assert 'abcabcabc'.tr('a', 'A') == 'AbcAbcAbc'

// a->b, b->a
assert 'abcabcabc'.tr('ab', 'ba') == 'bacbacbac'

// 範囲もいける
assert 'Groovy is so fun!'.tr('a-zA-Z', 'A-Za-z') == 'gROOVY IS SO FUN!'
assert '1234567890'.tr('0-9', '零一二三四五六七八九') == '一二三四五六七八九零'

参考: GroovyのString(Groovy JDK)にいつの間にか#trメソッドが追加されてた - No Programming, No Life

String replaceAll(String regex, Closure closure) [since v1.0]

// TODO 調査中

String replaceAll(Pattern pattern, String replacement) [since v1.6.1]

// TODO 調査中

String replaceAll(Pattern pattern, Closure closure) [since v1.6.8]

// TODO 調査中

String replaceFirst(String regex, Closure closure) [since v1.7.7]

// TODO 調査中

String replaceFirst(Pattern pattern, String replacement) [since v1.6.1]

// TODO 調査中

String replaceFirst(Pattern pattern, Closure closure) [since v1.7.7]

// TODO 調査中

*1:[http://d.hatena.ne.jp/fumokmm/20110123/1295752768:title:bookmark] 参照