以下の文章は、Object Computing, Inc(OCI) による「The Java News Brief」(2004年2月) に掲載された、Mark Volkmann による「Groovy - Scripting for Java」を、かくたにが翻訳したものです。原著者の許可を得て翻訳・公開しています。翻訳にあたっては、ma2さん、takaiさんから草稿に対してコメントをいただきました。ありがとうございます。
Groovy は日々発展中の新しい言語です。本記事の原文は2004年2月頃のものであり、当然その内容は Groovy の最新状況に追随しているわけではありません。翻訳時点での状況(1.0-beta4)をなるべく反映するようにはしましたが、正式版リリースまでにはさらに幾つもの変更が加えられることが予想されます。記事の記述と Groovy の現状とが異なる場合は、現状を優先してください。その一方で、そういった事項のこの記事へのフィードバックを歓迎します。連絡先はかくたにまでよろしくお願いします。フィードバックは記事の内容のみならず、誤訳誤記についても歓迎です。
Groovy はオープンソースのスクリプト言語である。Java で実装され、Java と緊密に統合されている。使うにあたっては J2SDK 1.4 が必要だ。Groovy は Ruby と Python といったスクリプト言語の機能のいくつかを Java に追加している。Groovy の機能には、動的型付け、クロージャ、容易なオブジェクト・ナビゲーション、そして、List や Map を取り扱うための簡潔な文法が含まれている。こうした機能(とさらに多くの機能)の詳細を本記事では取り上げる。
Groovy のウェブサイトから引用しよう。 "Groovy はあなたが Java プラットフォームで手早く、簡潔に、そして楽しく作業できるように設計されました―― Groovy は Python と Ruby の力強さを Java プラットフォームにもたらします。"
Groovy スクリプトではあらゆる Java クラスを利用できる。Groovy スクリプトは Java のバイトコード(.class ファイル)へとコンパイルできるので、普通の Java クラスから呼び出すこともできる。Groovy コンパイラである groovyc は、Groovy スクリプトと Java ソースファイルの両方をコンパイルすることができるが、現状の groovyc ではいくつかの Java 文法(たとえばネストしたクラス)が未サポートである。
理論上は、アプリケーション全体を Groovy で書くことができ、そのパフォーマンス特性は Java アプリケーションとほとんど等しくなるだろう。ここが、Groovy が他のスクリプト言語(Ruby、Python、Perl、そして BeanShell)とは異なるところだ。しかし現在、Groovy の性能は Java よりも劣る。 その原因のひとつは、生成された Groovy のバイトコードがコンストラクタと private/protected メソッドを呼び出すのにリフレクションを利用しているためだ。これは将来のリリースで解決されるだろう。
Groovy の開発は James Strachan と Bob McWhirter によって始められた。James は他にもたくさんのオープンソース・プロダクトの開発に携わっている(Jelly、dom4j、Jaxen、Betwixt、そして Maven)。Bob は Jaxen と Drools(オープンソースでオブジェクト指向の Java ルールエンジン)の創始者でもある。
本記事では Groovy の機能のすべてではないが、その多くを取り上げる。読者は Java の文法と Groovy の文法との比較ができる程度には Java の文法に親しんでいるものと想定する。
我われが、プログラミング言語の文法を善きものとするのは何かについて全面的に合意できれば、プログラミング言語の数はさほど多くは必要としないだろう。現実にはプログラミング言語が数多く存在していることを考えると、我われが合意に達していないことは明らかだ。本記事の読了後に、Java の文法でまったく充分で、Groovy の文法はシンタックス・シュガーが甘すぎて口に合わないな、と思うかもしれない。もし、あなたがそう判断したら、私としては Pat Niemeyer の BeanShell を http://www.beanshell.org から落としてきて調べてみることをお奨めする。そうではなく、Groovy の簡潔な文法が気に入ったなら、いやもうほんとグルーヴィだよね!!
Groovy をダウンロードするには、以下の手順に従う。
最新版は CVS にあり、これもダウンロード可能だ。説明についてはこちらを参照してほしい。
Groovy をインストールするには、以下の手順に従う。
Groovy のスクリプトを実行するには4種類の方法がある。 どの方法であっても、スクリプトはパースされて Java ソースに変換されたうえで、 Java バイトコードへとコンパイルされる。
groovysh
コマンドは、Groovy のステートメントを入力できる
対話的シェルを起動する。
ステートメントは Enter キーを押すことで複数行入力できる。
ステートメントは、execute
コマンド[*1]が入力されるまで評価されない。
groovyConsole
コマンドは Swing のウィンドウを起動する。ウィンドウの下部ペインに Groovy ステートメントを入力する。Actions メニューから Run を選ぶとステートメントが実行される。出力は上部ペインに表示される。File メニューを使用して、スクリプトをファイルから開いたり、ファイルに保存することができる。
Groovy のスクリプトファイル (一般的には拡張子 .groovy のファイル)を次のようなコマンドから実行することができる。groovy script-name.groovy
Groovy スクリプトは groovyc script-name.groovy
コマンドを使用して Java の .class ファイルへとコンパイルできる。スクリプトにはルーズ・ステートメント[*2]があったとしても、生成された .class ファイルには main メソッドが含まれているので、java script-name
コマンドを使って Java アプリケーションとして実行できる。main メソッドの内容については後述する。実行にあたってはクラスパスに Groovy の lib ディレクトリにある groovy*.jar
と asm*.jar
を追加する必要がある。
なんと、この作業を行うためのカスタム Ant タスクも用意されている!
org.codehaus.groovy.ant.Groovyc
クラスがそうだ。
[*1]訳注: "go"コマンドでも実行可能。
[*2]訳注: 明示的にはどのクラスにも属さないステートメント。
まずは Java と Groovy の文法上の主な違いを取り上げる。細かいものについては後述する。
java.lang
と groovy.lang
、そして groovy.util
のクラスは自動的にインポートされる。
[*3]訳注: 1.0beta4だとすでに評価値が返るようになっているようだ。
[*4]訳注: これは原文の誤り。Javaのメソッドスコープのデフォルトは package-private。
変数、プロパティ、メソッド/クロージャの引数、そしてメソッドの戻り値において、型の宣言は省略できる。それらはどんな型でもとることができる。また、後から異なる型を割り当てることもできる。もちろん、プリミティブ型も利用可能だ(auto-boxing される)。型変換のの多くは必要に応じて自動的に行われる。たとえば、String やプリミティブ型(int とか)と型ラッパークラス(Integer とか)との間での型変換がそうだ。このおかげで、プリミティブ型をコレクションに追加することができる。
Groovy は java.lang.Object や java.lang.String といった標準 Java クラスに数多くのメソッドを追加している。詳細は http://groovy.codehaus.org/groovy-jdk.html を参照のこと。ここでは Object クラスに追加されたものを示す。その他のものについては後述する。
<class-name@hashcode property-name=property-value ...>
の形式で文字列を返す。
たとえば、 <Car@ef5502 make=Toyota model=Camry>
といった具合だ。
これらの static な print メソッドはオブジェクトの toString
値を出力する。
たとえば、 print car
あるいは println car
と書く。
リフレクションを利用して動的なメソッド呼び出しを行う。
文法は、 object.invokeMethod(method-name, argument-array)
だ。
次のサンプルは、4を出力する。
s = 'abcabc' // Java の文字列 method = 'indexOf' args = ['b', 2] println s.invokeMethod(method, args) // 4
リテラル文字列は、シングルクォートまたはダブルクォートで囲む。ダブルクォートを使った場合は、埋め込み値を利用できる。埋め込み値の文法は、 ${expression}
だ。Rubyでの # の代わりに $ を使用する。ダブルクォートで囲まれた文字列にひとつ以上の埋め込み値が含まれる場合、その文字列は groovy.lang.GString
オブジェクトで表現される。そうでない文字列は java.lang.String
だ。GString は必要に応じて java.lang.String
へと強制的に変換される。
groovy.lang.GString といった Groovy クラスの Javadoc は、 http://groovy.codehaus.org/apidocs/ で読むことができる。
埋め込み値は toString メソッドを実装する際にとても便利だ。たとえば、
String toString() { "${name} is ${age} years old." }
複数行文字列を生成する方法は3種類ある。以下のサンプルはすべて同じ結果になる。最後のサンプルはヒアドキュメント("here-doc")と呼ばれるものだ。3字以内の文字をデリミタ文字列として指定すると、文字列の値には最初と2番目のデリミタ間に現れる文字が値として設定される。"EOS" ("End Of String" の略)はよく使われるデリミタ値だが、もちろん他の文字も使用できる。
s = " This string spans three \"lines\" and contains two newlines." s = """ This string spans three "lines" and contains two newlines.""" s = <<<EOS This string spans three "lines" and contains two newlines. EOS
上記のコードで,最後の改行が残らないことに注意。
以下に java.lang.String
へ追加されているメソッドを示す。
引数の部分文字列を含んでいるかを調べる。
'Groovy'.contains('oo')
は true
を返す。
引数の部分文字列の出現回数をカウントする。
'Groovy Tool'.count('oo')
は 2
を返す。
引数で与えられたデリミタを使用して文字列をトークン化し、そのトークンのリストを返す。デリミタ引数の指定は省略できる。デフォルトでは空白文字が使用される。
'apple^banana^grape'.tokenize('^')
は ['apple', 'banana', 'grape']
を返す。
対象文字列から、引数として与えられた部分文字列のうち最初に出現するものを削除する。
'Groovy Tool' - 'oo'
は 'Grvy Tool'
を返す。
引数で与えられた回数だけ対象文字列を繰り返す。
'Groovy'
* 3 は 'GroovyGroovyGroovy'
を返す。
まずは、J2SE 1.4 での正規表現をおさらいしておこう。次に、Groovy がどのようにこれを拡張しているかを議論する。
J2SE 1.4 の正規表現は java.util.regex
パッケージのクラスでサポートされる。Pattern オブジェクトは、コンパイルされた正規表現を表し、Pattern.compile("pattern")
メソッドで生成される。正規表現の文法は Pattern クラスの Javadoc で解説されている。Matcher オブジェクトはマッチ対象文字列に対するマッチングの結果を保持する。Matcher オブジェクトは pattern.matcher("text")
メソッドで生成される。単に文字列がパターンにマッチするのかを知りたいのであれば、matcher.matches()
メソッドを使う。パターン内でバックスラッシュを使うには、さらに追加のバックスラッシュが必要だ。
Groovy は正規表現を3種類の演算子でサポートする。
~"pattern"
は Pattern オブジェクトを生成する。これは、Pattern.compile("pattern")
と同じ意味である。
"text" =~ "pattern"
は Matcher オブジェクトを生成する。これは Pattern.compile("pattern").matcher("text")
と同じ意味である。
"text" ==~ "pattern"
は boolean 値を返す。これは Pattern.compile("pattern").matcher("text").matches()
と同じ意味である。他の追加されたメソッドについては Pattern クラスと Matcher クラスの Javadoc を参照のこと。
サンプルを示そう。
pattern = "\\d{5}" // 郵便番号にマッチ(5桁) text = "63304" // 郵便番号 println text ==~ pattern // "true" が出力される m = text =~ pattern println m.matches() // "true" が出力される // 次の例では、パターンはリテラル文字列でなければならない。 // 変数は使えない。 p = ~"\\d{5}" m = p.matcher(text) println m.matches() // "true" が出力される
Groovy で書かれたスクリプトのソースは、普通は拡張子 ".groovy" のファイルだ。スクリプトには(自由な順序で)ルーズ・ステートメント、クラスに属さないメソッド定義、そしてクラス定義が含まれている。
たとえば、
// ルーズ・ステートメント println 'loose statement' myMethod 'Mark', 19 println new MyClass(a1:'Running', a2:26.2) // クラスに属さないメソッドの定義 def myMethod(p1, p2) { println "myMethod: p1=${p1}, p2=${p2}" } // 2つのプロパティと1つのメソッドを持つクラスの定義 class MyClass { a1; a2 String toString() { "MyClass: a1=${a1}, a2=${a2}" } }
メソッドとクラスの定義は、使用されるよりも前になくてもよい。ルーズ・メソッド[*5]は該当するスクリプトファイルに対応するクラスの static メソッドとしてコンパイルされる。たとえば、foo
という名前のルーズ・メソッドが、Bar.groovy
という名前のスクリプトファイルにあれば、Bar
クラスの static メソッド foo
としてコンパイルされる。ルーズ・ステートメントは、自動生成される main
メソッドから呼び出される run
メソッドにまとめられる。
groovyc
を使用してスクリプトをコンパイルした場合、コンパイル後のクラスには、すべてのルーズ・ステートメントがまとめられた run
メソッドと、この run
メソッドを呼び出す main
メソッドが自動で追加される。
いまのところ、スクリプトから他のスクリプトを実行するためには、実行したいスクリプトをコンパイルし、インポートしなければならない。この不具合は近いうちに修正されるはずだ。
[*5]訳注: 明示的にはどのクラスにも属さないメソッド。
Groovy は特定の演算子のオーバーロードをサポートしている。オーバーロード可能な演算子はそれぞれ、特定のメソッドに対応づけられる。対応づけられたメソッドを実装することで、そのクラスのオブジェクトはメソッドに対応する演算子をオーバーロードできる。オーバーロード対応メソッドを、様ざまなパラメータの型に向けてオーバーロードすることもできる。
a == b
は a.equals(b)
に対応する。
a != b
は !a.equals(b)
に対応する。
a === b
は Java の a == b
に対応する。
a <=> b
は a.compareTo(b)
に対応する。
a > b
は a.compareTo(b) > 0
に対応する。
a >= b
は a.compareTo(b) >= 0
に対応する。
a < b
は a.compareTo(b) < 0
に対応する。
a <= b
は a.compareTo(b) <= 0
に対応する。
比較演算子は null をハンドルするので、NullPointerException は絶対に発生しない。null はどんなオブジェクトよりも小さいものとして扱われる。
Groovy の ==
演算子は、両辺のオブジェクトが等価であるかをテストし、===
演算子は両辺がメモリ上で同一のオブジェクトであるかをテストすることに注意。
compareTo
メソッドは int
を返す。返す数値は、a < b の場合は負の値、a > b の場合は正の値、同値の場合は0である。
a + b
は a.plus(b)
に対応する。
a - b
は a.minus(b)
に対応する。
a * b
は a.multiply(b)
に対応する。
a / b
は a.divide(b)
に対応する。
a++
と ++a
は a.increment(b)
に対応する。
a--
と --a
は a.decrement(b)
に対応する。
a[b]
は a.get(b)
に対応する。
a[b] = c
は a.put(b, c)
に対応する。
クロージャとは、パラメータを受け取ることも可能なコード片のことである。個々のクロージャは、groovy.lang.Closure
を継承した新しいクラスとしてコンパイルされる。クロージャの継承クラスは call
メソッドを持つ。このメソッドはクロージャの実行と、パラメータを渡すために利用される。クロージャは、自身が生成されたスコープにある変数を読み書きできる。クロージャ内部で生成された変数は,クロージャが起動されたスコープで有効になる。クロージャは変数に代入しておくことができ、それをメソッドに引数として渡すこともできる。この特徴は List や Map、そして String のメソッドで威力を発揮する。詳しくは後述する。
クロージャを定義する文法は、
{ comma-separated-parameter-list | statements }
となっている。たとえば、
closure = { bill, tipPercentage | bill * tipPercentage / 100 } tip = closure.call(25.19, 15) tip = closure(25.19, 15) // 上の行と同じ意味
間違った数のパラメータを渡すと、 IncorrectClosureArgumentException
が発生する。
パラメータが1つのクロージャでは、it
キーワードを使うことができる。このとき、パラメータリストを省略して、代わりに it
を使ってステートメント中で参照できる。たとえば、以下のクロージャはいずれも同じ意味である。
{ x | println x } { println it }
以下は List とクロージャとを引数に取るメソッドのサンプルである。このサンプルではルーズ・メソッドとして書いたが、もちろんクラスのメソッドにもできる。このメソッドは list の各要素を引数にクロージャを実行させながら list をイテレートする。各クロージャを実行した結果として true を返してきた要素を newList に追加する。呼び出し元には newList を返す。Groovy は find
と findAll
メソッドを提供しているので、普通は以下のサンプルのようには書かないことに注意。
def List myFind(List list, Closure closure) { List newList = [] for (team in list) { if (closure.call team) newList.add team } newList }
myFind メソッドを使用するコードも示しておこう。
class Team { name; wins; losses } teams = [] teams.add new Team(name:'Rams', wins:12 , losses:4) teams.add new Team(name:'Raiders', wins:4 , losses:12) teams.add new Team(name:'Packers', wins:10 , losses:6) teams.add new Team(name:'49ers', wins:7 , losses:9) winningTeams = myFind(teams) { it.wins > it.losses } winningTeams.each { println it.name }
実際には myFind
のようなメソッドは必要ない。というのも、List クラスにはあらかじめ findAll
メソッドが用意されているからだ。使い方はこうだ。
winningTeams = teams.findAll { it.wins > it.losses }
以下は Groovy Bean のシンプルなサンプルだ。
class Car { String make String model }
このクラスはプロパティを2つ宣言しているが、メソッドは定義していない。しかし、その舞台裏では様ざまなことが行われている。クラスやプロパティ、そしてメソッドのスコープはデフォルトでは public である。public と protected のプロパティは、結果としては public/protected な getter/setter メソッドが自動生成された private フィールドになる。これらのメソッドをオーバーライドすることで独自の振る舞いを持たせられる。プロパティが明示的に private
と宣言された場合には、getter/setter は自動生成されない。
上記の Groovy コードは、以下の Java コードと同じ意味である。
public class Car { private String make; private String model; public String getMake() { return make; } public String getModel() { return model; } public void setMake(String make) { this.make = make; } public void setModel(String model) { this.model = model; } }
Groovy Bean として生成されるクラスは java.lang.Object
クラスを継承し、かつ groovy.lang.GroovyObject
インタフェースを実装する。このクラスに追加されるメソッドは、getProperty
、setProperty
、getMetaClass
、setMetaClass
そして invokeMethod
である。groovy.lang.MetaClass
はクラスに対してメソッドを実行時に追加することを可能にする。
Groovy Bean では名前付きパラメータを利用してインスタンスを生成することができる。たとえば次のコードでは、まず Car の引数無しコンストラクタが呼ばれ、次にそれぞれのプロパティの setter メソッドが呼ばれる。
myCar = new Car(make:'Toyota', model:'Camry')
Groovy List は java.util.ArrayList
のインスタンスだ。リストはカンマ区切りで並べた値を角括弧で囲むことで生成できる。たとえば、
cars = [new Car(make:'Honda', model:'Odyssey'), new Car(make:'Toyota', model:'Camry')] println cars[1] // Camry を参照する for (car in cars) { println car } // Car の toString メソッドを呼び出す class Car { make; model String toString() { "Car: make=${make}, model=${model}" } }
リストの要素をリストの最後からアクセスするには、負の値をインデックスに使用する。
空のリストは []
で生成できる。例としては、
cars = []
リストへ要素を追加する方法は2種類ある。
cars.add car cars << car
リストは配列から array.toList()
メソッドで生成できる。配列はリストから list.toArray()
メソッドで生成できる。
Groovy はいくつかのメソッドを java.util.List
に追加している。
引数に与えられたオブジェクトと等価な要素がリスト内にいくつあるかを数える。
[1, 2, 3, 1].count(1)
は 2
を返す。
コレクションの immutable(変更不能)なコピーを生成する。生成にあたっては java.util.Collections
の static な unmodifiableList
メソッドを利用している。たとえば、
list = [1, 2, 3].immutable() list.add 4 // java.lang.UnsupportedOperationException がスローされる
2つのリストに共通の要素を含んだ新しいリストを生成する。
[1, 2, 3, 4].intersect([2, 4, 6])
は [2, 4]
を返す。
リストの要素それぞれに対する toString
の値の間に、引数の文字列を挿入して連結する。たとえば、キャレットを区切り文字としてリストに含まれるすべての文字列を連結したければ ['one', 'two', 'three'].join('^')
と書く。これは "one^two^three"
を返す。
リストの要素を並び替えた新しいリストを作成する。java.util.Comparator
かクロージャを渡すことでソート順を変更することができる。
fruits = ['kiwi', 'strawberry', 'grape', 'banana'] // 次の行は [banana, grape, kiwi, strawberry] を返す。 sortedFruits = fruits.sort() // 次の行は [kiwi, grape, banana, strawberry] を返す。 sortedFruits = fruits.sort {l, r | return l.length() <=> r.length()}
上記の最後にある sort
メソッド呼び出しは、クロージャをパラメータとして渡すサンプルでもある。Groovy にはクロージャをパラメータとして渡せるメソッドが数多く用意されている。
Groovy Bean では複数のプロパティを使用したソートが簡単にできる。たとえば、Player
という Bean に name
、age
、score
というプロパティがあるとしよう。この Bean のリストが players
に格納されていて、それを age
と score
の値を基に並び替えるには、次のように書く。
players.sort { [it.age, it.score] }
java.util.Comparator
かクロージャを渡すことで、判定方法を変更できる。たとえば、以下のコードはリストから最小値と最大値を検索する。[5, 9, 1, 6].min()
は 1
を返す。[5, 9, 1, 6].max()
は 9
を返す。
リストの要素または文字列[*7]にある文字を、逆順に並び替える。
[1, 2, 3].reverse()
は [3, 2, 1]
を返す。
Groovy は java.util.List
オブジェクトで使用できるように、加算と減算の演算子をオーバーロードしている。
2つのリストを結合した新しいリストを生成する。重複する要素があっても削除されない。
[1, 2, 3] + [2, 3, 4]
は [1, 2, 3, 2, 3, 4]
を返す。
最初のリストから二番目のリストにある要素すべてを取り除いたリストを生成する。
[1, 2, 3, 4] - [2, 4, 6]
は [1, 3]
を返す。
リストの要素にプリミティブ型が含まれていなければ、要素の比較には equals
が使われる。
[*6]訳注: 1.0-beta4 では文字列に対してmin/maxは実行できない。
[*7]訳注: 1.0-beta4 では文字列に対してreverseは実行できない。「"abc".reverse()」のようにカッコを付ければ実行可能でした。
Groovy Map は、java.util.HashMap
のインスタンスだ。マップはカンマ区切りで並べたキー/値のペアを角括弧で囲むことで生成できる。キーと値とはコロンで区切る。たとえば、
players = ['baseball':'Albert Pujols', 'golf':'Tiger Woods'] println players['golf'] // Tiger Woods を出力する println players.golf // Tiger Woods を出力する for (player in players) { println "${player.value} plays ${player.key}" } // これは上のループと同じ意味 players.each {player | println "${player.value} plays ${player.key}" }
空のマップは [:]
で生成できる。以下はその例だ。
players = [:]
Groovy の switch
文では、クラス、List、Range、Pattern など、あらゆるオブジェクトを使える。case
文は isCase
メソッドを使って値を比較する。数多くのオーバーライドされた isCase
メソッドが提供されている。特定の型に向けておいてオーバーロードされない限り、isCase
は equals
メソッドを使用する。
case
がクラス名である場合には、isCase
メソッドは instanceof
を使用する。
isCase
メソッドは、独自に作成したクラスでオーバーライドすることができる。
様ざまな異なる型の値に対応する switch 文のサンプルを示す。
switch (x) { case 'Mark': println "got my name" break case 3..7: println 'got a number in the range 3 to 7 inclusive' break case ['Moe', 'Larry', 'Curly']: println 'got a Stooge name' break case java.util.Date: println 'got a Date object' break case ~"\\d{5}": println 'got a zip code' break default: println "got unexpected value ${x}" }
Range(範囲オブジェクト)は ".." と "..." 演算子を使用して生成する。サンプルを示そう。
3..7
は「3 から 7」を表す Range を生成する。
3...7
は「3 から 6」を表す Range を生成する。
"A".."D"
は「"A" から "D"」を表す Range を生成する。
"A"..."D"
は「"A" から "C"」を表す Range を生成する。
範囲オブジェクト は java.util.AbstractList
を継承した groovy.lang.Range
インターフェースを実装するクラスで表現される。Range オブジェクトは immutable なリストだ。Range インタフェースは getFrom
と getTo
メソッドを追加しているので、下限と上限を取得できる。2種類の Range 実装が提供されている。 groovy.lang.IntRange
は整数値に限った範囲を扱う。IntRange には contains
メソッドが追加されており、値が範囲内にあるかをテストできる。
groovy.lang.ObjectRange
ではその他のいかなる型でも範囲に含めることができる。ここでも java.lang.Comparable
を実装している場合に限られる。
範囲オブジェクトはループ構造で役に立つ。利用例は次セクションで取り上げる。
値の範囲を通じた繰り返し処理を行うには6つの方法がある。
for (i in 1..1000) { println i }
i = 1 while (i <= 1000) { println i; i++ }
(1..1000).each { println it }
1000.times { println it } // 0 から 999 まで。
1.upto(1000) { println it }
1.step(1001, 1) { println it } // 1から1000まで。 // パラメータの値のひとつ手前で終了する。
List、Map、そしてString のメソッドのなかにはクロージャをパラメータとして渡せるメソッドがある。
コレクションの要素または文字列内の文字に対して繰り返し処理を行う。java.util.Iterator
の代わりに利用することで、より簡潔なコードを記述できる。たとえば、リストの要素の数値をそれぞれ表示させようと思えば、
[5, 9, 1, 6].each {x | println x}
あるいは
[5, 9, 1, 6].each {println it}
と書ける。
コレクションまたは文字列を変換して、新しいコレクション(または文字列)を作成する。たとえば、リストの各要素の値を2倍した新しい要素を持つリストを作成するには、
doubles = [5, 9, 1, 6].collect {x | x * 2}
とする。これは doubles
に [10, 18, 2, 12]
をセットする。
与えられた条件に合致するものをコレクションの要素また文字列内の文字から検索し、最初に出現した要素を返す。たとえば、リストの中から5よりも大きい最初の要素を検索したければ、
[5, 9, 1, 6].find {x | x > 5}
と書くと 9
が返ってくる。
与えられた条件に合致するものをコレクションの要素または文字列内の文字から検索し、出現した要素すべての配列を返す。たとえば、リストの中から5よりも大きい要素をすべて検索したければ、
[5, 9, 1, 6].findAll {x | x > 5}
と書くと [9, 6]
が返ってくる。
コレクションの要素または文字列内の文字すべてが、与えられた条件に合致するかを調べる。たとえば、リストに含まれる数字がすべて7より小さいかを調べたいのであれば、
[5, 9, 1, 6].every {x | x < 7}
と書くと false
が返ってくる。
コレクションの要素または文字列内の文字に、与えられた条件に合致するものがひとつでも存在するかを調べる。たとえば、リストに含まれる数字に7より小さいものが含まれているかを調べたいのであれば、
[5, 9, 1, 6].any {x | x < 7}
と書くと true
が返ってくる。
繰り返しの初回には、引数に渡された値が使われ、それぞれの繰り返しでの評価結果が次回以降に渡されていく。たとえば、5の階乗を求めるには、次のように書ける(あまり見かけないやり方ではあるが)。
factorial = [2, 3, 4, 5].inject(1) { prevResult, x | prevResult * x }
このクロージャは、4回実行される。
1回目: 1 * 2
2回目: 2 * 3
3回目: 6 * 4
4回目: 24 * 5
結果は、120
になる。
サンプルコードの(...) はコードの省略を示す。
file = new File('myFile.txt') file.eachLine { println it } lineList = file.readLines()
file = new File('myFile.txt') file.eachByte { println it } byteList = file.readBytes()
dir = new File('directory-path') dir.eachFile { file | . . . }
Reader または InputStream から読み込むこれらのメソッドは、例外が起きてもリソースを閉じることが保証されている。
file.withReader { reader | . . . } reader.withReader { reader | . . . } inputStream.withStream { is | . . . }
処理中に例外が投げられたとしても、Writer または OutputStream への書き込みを閉じることを保証する。
file.withWriter { writer | . . . } file.withPrintWriter { pw | . . . } file.withOutputStream { os | . . . } writer.withWriter { writer | . . . } outputStream.withStream { os | . . . }
String に追加するには、
s = 'foo' s = s << 'bar'
StringBuffer に追加するには、
sb = new StringBuffer('foo') sb << 'bar'
List に追加するには、
colors = ['red', 'green'] colors << 'blue'
出力ストリームの末尾に追加するには、
w = new File('myFile.txt').newWriter() w << 'foo' << 'bar' w.close()
オブジェクト構造(Object Graphs)を XPath 風の文法で辿るには、ドット(".")演算子を使用する。NullPointerException
の危険を回避するには、"->" を "." の代わりに使用する。たとえば、
class Team { String name Person coach players = [] } class Person { String name } p = new Person(name:'Mike Martz') t = new Team(name:'Rams', coach:p) // 次の行は、team.getCoach().getName と同じ結果を出力する println "coach = ${t.coach.name}" t = new Team(name:'Blues') // 次の行は null を返すのだが、 // NullPointerException は発生しない println "coach = ${t->coach->name}" // 次の行は NullPointerException が発生する println "coach = ${t.coach.name}"
あるオブジェクトの Class
オブジェクトを取得したい場合に、Java では someObject.getClass()
と書く。Groovy で同じことを行うには、someObject.class
と書く。
クラス名から Class
オブジェクトを取得したい場合は、Java でも Groovy でも SomeClass.class
または Class.forName("pkg.SomeClass")
と書く。
Groovy の GString
クラスのメソッド名を一覧表示したければ、次のように書く。
GString.class.methods.each { println it.name }
Java の java.util.List
インタフェースのメソッド名を一覧表示したければ、次のように書く。
java.util.List.class.methods.each { println it.name }
クラスには、未実装のメソッドに対する呼び出しをキャッチするコードを書くことができる。たとえば、
o = new CatchCall() // 次の行は "unknown method Mark called with [19]" と表示する println o.foo("Mark", 19) class CatchCall { invokeMethod(String name, Object args) { try { return metaClass.invokeMethod(this, name, args) } catch (MissingMethodException e) { // ここに確実に呼び出せるメソッド名と引数での呼び出しを // 行うロジックを書いてもよい return "unknown method ${name} called with ${args}" } } }
Groovy Markup は 上述の invokeMethod
メソッドを使用して存在しないメソッドの呼び出しをキャッチし、それを「ノード」に変換する。メソッドの引数はノードの属性として扱われる。メソッドに渡されたクロージャはノードのコンテンツとして扱われる。Groovy Markup では様ざまな利用法が用意されている:
NodeBuilder
)DOMBuilder
)SAXBuilder
)MarkupBuilder
)AntBuilder
)SwingBuilder
)
これに加えて、独自のビルダを作成するには groovy.util.BuilderSupport
クラスを継承すればよい。
MarkupBuilder
を使用して HTML を生成するサンプルを示す。
import groovy.xml.MarkupBuilder
mb = new MarkupBuilder()
mb.html() {
head() {
title("This is my title.")
}
body() {
p("This is my paragraph.")
}
}
println mb[*8]
これが生成する HTML は次のようになる。
<html> <head> <title>This is my title.</title> </head> <body> <p>This is my paragraph.</p> </body> </html>
MarkupBuilder
を使用して XML を生成するサンプルを示す。
import groovy.xml.MarkupBuilder;
mb = new MarkupBuilder()
mb.autos() {
auto(year:2001, color:'blue') {
make('Toyota')
model('Camry')
}
}
println mb[*9]
これが生成する XML は次のようになる。
<autos> <auto year='2001' color='blue'> <make>Toyota</make> <model>Camry</model> </auto> </autos>
[*8]訳注: mb.html() メソッドの実行時点で標準出力にHTMLが出力されるので、サンプルの目的を考えるとこのステートメントは冗長。MarkupBuilderの実行結果を文字列としてmbに設定したい場合は懸田さんによるサンプルコードを参照。
[*9]訳注: mb.autos() メソッドの実行時点で標準出力にXMLが出力されるので、サンプルの目的を考えるとこのステートメントは冗長。MarkupBuilderの実行結果を文字列としてmbに設定したい場合は懸田さんによるサンプルコードを参照。
Groovy は JDBC を簡単にする。groovy.sql.Sql
クラスはクエリの実行と ResultSet
行のイテレートを簡単に行う方法を提供する。次のサンプルでは、MusicCollection
はデータベースの名前(この場合は、登録された ODBC データソース)で、 Artists
がデータベースのテーブル名、Name
がテーブルのカラム名だ。
import groovy.sql.Sql dbURL = 'jdbc:odbc:MusicCollection' jdbcDriver = 'sun.jdbc.odbc.JdbcOdbcDriver' sql = Sql.newInstance(dbURL, jdbcDriver) sql.eachRow('select * from Artists') { println it.Name }
Groovlet は Groovy による、サーブレットと JSP の代替案である。Groovlet は以下の暗黙変数を提供する。
out
- HttpServletResponse#getWriter
メソッドが返すものrequest
- HttpServletRequest
session
- HttpSession
Groovlet のサンプルを示す。このコードは SimpleGroovlet.groovy
みたいな名前で保存する。また、コードは HTML の出力にヒアドキュメントを使用している。
out.println <<<EOS <html> <head> <title>My Simple Groovlet</title> </head> <body> <h1>My Simple Groovlet</h1> <p>Today is ${new java.util.Date()}.</p> </body> </html> EOS
GroovyServlet
は Groovlet をコンパイルし、ファイルが変更されるまでキャッシュする。再コンパイルは変更時に自動的に行われる。 GroovyServlet
は web.xml に登録しなければならない。
以下は web.xml
ファイルのサンプルだが、GroovyServlet
の登録部分だけしか示していない。
<?xml version="1.0"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>Groovy</servlet-name> <servlet-class>groovy.servlet.GroovyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Groovy</servlet-name> <url-pattern>*.groovy</url-pattern> </servlet-mapping> </web-app>
Groovlet のデプロイには Ant を使うのが良いだろう。基本的な手順は:
*.groovy
) をトップレベルの階層に配置WEB-INF
ディレクトリに web.xml
を配置groovy*.jar
と asm*.jar
ファイルを WEB-INF/lib
ディレクトリに配置これを行うための Ant ビルドファイルはこうだ。
build.dir=build src.dir=src # Groovlet の置かれているディレクトリ groovy.dir=${src.dir}/groovy # web.xml の置かれているディレクトリ web.dir=${src.dir}/web # WAR ファイルの生成先パス war.file=${build.dir}/${ant.project.name}.war # WAR ファイルのデプロイ先 webapps.dir=${env.TOMCAT_HOME}/webapps # WAR ファイルに含めるべき JAR ファイル asm.jar=${env.GROOVY_HOME}/lib/asm-1.4.1.jar groovy.jar=${env.GROOVY_HOME}/lib/groovy-1.0-beta-4-snapshot.jar
<project name="GroovletExample" default="deploy"> <property environment="env"/> <property file="build.properties"/> <target name="prepare"> <mkdir dir="${build.dir}"/> </target> <target name="war" depends="prepare" description="creates WAR file"> <war destfile="${war.file}" webxml="${web.dir}/web.xml"> <fileset dir="${groovy.dir}"/> <lib file="${groovy.jar}"/> <lib file="${asm.jar}"/> </war> </target> <target name="deploy" depends="war" description="deploys WAR file"> <delete dir="${webapps.dir}/${ant.project.name}"/> <delete file="${webapps.dir}/${war.file}"/> <copy file="${war.file}" todir="${webapps.dir}"/> </target> </project>
サンプルの Groovlet がデプロイされたら、ウェブブラウザから http://localhost:8080/GroovletExamples/SimpleGroovlet.groovy でアクセスできる。GroovletExample は Web アプリケーションの名前で、SimpleGroovylet.groovy が Groovlet の名前だ。このマッピングは web.xml
に登録されている GroovyServlet
の url-pattern
で行われる。
Groovy はまだ完璧ではない。Groovy の抱える問題を見るには、 http://groovy.codehaus.org にアクセスして Issue Tracker のリンクをクリックすればよい。 ここでは、報告されている問題の中からいくつかをピックアップする。 割り当てられている問題番号も併記した。
*
の利用が未サポート(84)[*14]。
[*10]訳注: 1.0-beta4 でサポートされた。
[*11]訳注: 1.0-rc1 でサポート予定か?
[*12]訳注: 1.0-beta4 でサポートされた。
[*13]訳注: 1.0-rc1 でサポート予定。
[*14]訳注: 1.0-beta4 でサポートされた。
[*15]訳注: 1.0最終リリースでサポート予定。
[*16]訳注: 1.1でサポート予定。
さて本記事では…… Groovy の文法と機能のいくつかをざっくり流してみた。 Groovy の提供する Java ショートカットは、あなたの生産性を上げそうだろうか? もっと Groovy を楽しんでみたいと思っただろうか? コードは読みやすくなりそうだろうか? それとも読みづらくなりそうだろうか? あなたからのフィードバックをとても楽しみしている。是非、[email protected]
にメールして欲しい[*17]。また、フィードバックは Groovy のメーリングリスト(ここに詳細がある)でも共有したいと思う。
[*17]訳注: 日本語訳に関するフィードバックは [email protected] へお願いします。