Groovy
出典: フリー百科事典『ウィキペディア(Wikipedia)』
Groovy(グルービー)は、Java仮想マシン (JVM) 上で動作するスクリプト言語である。
Groovyの処理系はオープンソースソフトウェアであり、James StrachanとBob McWhirterらを中心に、オープンソース開発サイトであるcodehaus上でBSD/Apacheライクなライセンスにて開発・公開されている。Groovyは2003年8月27日に開発が開始された(CVSへの最初のコミットがなされた)。
目次 |
[編集] 概要
GroovyはJVM上で動作する言語処理系および言語の名称であり、Javaとの直接的な連携を特徴とする。例えばGroovyからすべてのJ2SE APIや、Javaで書かれた任意のサードパーティ製のコンパイル済みのライブラリなどを呼び出すことができる。言語の記述能力としては、Javaで記述できることは、無名内部クラスの定義など一部の例外を除き基本的にGroovyでも記述することができる。逆に言うとJavaで記述できない機能は記述できないが、Javaと同様にC言語などで書かれたネイティブメソッドなどは呼び出すことができる。
Groovyは動的な言語であり、直接スクリプトを実行することができる。Groovyコード断片をコマンドラインに与えワンライナーとして実行することも可能である。なおこの時、中間的にJavaソースコードが生成されることはなく、バイトコードがメモリ上に生成されて直接実行される。また、groovycコマンド(groovyコンパイラ)を使ってクラスファイルをあらかじめ生成しておくこともできる。いずれにせよGroovyコードは内部的にはJavaバイトコードに変換されてJVM上で実行される。
このとき、GroovyコードもJavaコードも、JVMからみると両方ともJavaバイトコードとして解釈実行されるという意味で区別がない。Groovyのこのような仕組みから、GroovyはJavaと極めて親和性が高く、Java技術で培われてきた開発インフラやライブラリ、ノウハウ、ツール、JVM最適化技術などの多くをそのまま流用することができる。Groovyから生成したクラスファイルは通常のクラスファイルであるので、Javaクラスファイルを要求するプラグインなどをGroovyで記述することも容易である。
Groovyは、同じ実行時システムを共有する、Javaコードの別の表記法だと考えることもできる。
[編集] 言語仕様
Groovyの言語仕様はJavaのそれをベースとしており、基本的にJavaプログラマにとって慣れ親しみやすいものである。Groovyはスクリプト言語として大幅に簡易化された記述を許している。以下に簡略な記述を可能とするGroovy言語の特徴を示す。
- Groovyコードはクラス定義中にある必要はなく、クラス定義の外側でのメソッドの定義や実行文の記述が可能である。
- 変数の型宣言は不要である。宣言をしなかった場合Object型として扱われ、メソッド呼び出しはすべて動的ディスパッチによって解決される(変数の型の宣言をすることも可能であり、静的なスタイルと動的なスタイルの場合に応じた使い分けることができる)。メソッドの引数や返り値の型宣言も同様である。
- メソッド呼び出しの括弧は省略できる。
- 行末のセミコロンは省略できる。
- リストやマップの初期化を記述する組み込み構文を持つ。
- 演算子のオーバーロード定義(ユーザ定義演算子)が可能である。
- リストやマップを扱う演算子が定義されている。
- BigDecimal、BigInteger型などについては四則演算がオーバーロード定義されている。
- 検査例外をthrowsするメソッドを呼び出す際にもtry/catchで囲んだり呼び出し側メソッドをthrows宣言する必要はない。
- プリミティブ型はリファレンス型と同様に扱うことができる(明示的な変換を行う必要はない)。
- switch/case文は任意の型に対して分岐することができるように拡張されている。
- for(i in ..)形式のfor文(J2SE 5.0("Tiger")のそれと同様)。
- if文やwhile文、3項演算子('?~:~')の条件節では0やnullの値は偽として扱われる(boolean型の値である必要がない)。
- J2SEの正規表現クラスを扱うための組み込みの演算子(=~や==~など)が用意されている。また構文上も特別扱いされておりPerlやRubyと似た使用ができる。
- 文字列定数中に任意のGroovyの式を埋め込むことができる(${}の記法を用いる。GStringと呼ぶ)。
- Getter、Setterメソッドの自動生成。
@Property String name = "名前"
- フィールドアクセスの記法でGetter、Setterメソッドを呼び出すことができる。
- デフォルト引数(メソッド・コンストラクタ呼び出しチェインの自動生成)。
class Main { static void main(String[] array) { def pojo = new Pojo(name:"名前") println pojo.getName() } } class Pojo { @Property String name }
class Main { static void main(String[] array) { greet() greet("foo") /* 標準出力結果 Hello World foo */ } static void greet(String message = "Hello World") { println message } }
- 名前つき引数でのメソッド呼び出し。
- アクセス修飾子のデフォルトはpublicである。
- java.lang、java.io、java.math、java.net、java.util、groovy.lang、groovy.utilは自動でインポートされます。
- groovyファイルで、定義したクラスはGroovyObjectインターフェースを暗黙で実装し、クラスの外で定義したフィールドやメソッドはScript抽象クラスの実装クラスのフィールドやメソッドとして定義されたと見なします。
- Groovyは未実装のフィールドの参照と代入、未実装のメソッドの起動をキャッチしGroovyObjectのメソッドを起動します。未実装メソッドのキャッチ、Expandoクラス、クロージャにより、独自のクラスの場合はオープンクラス(open classes)に見えるコードが記述可能です。
GroovyObject#getProperty(String name) GroovyObject#setProperty(String name, Object value) GroovyObject#invokeMethod(String name, Object arguments)
class Main { static void main(String[] array) { def mockOpenClass = new MockOpenClass() //def mockOpenClass = new Expando() mockOpenClass.greetingMessage = "Hello World" mockOpenClass.greet = {println greetingMessage} mockOpenClass.greet() mockOpenClass.message = "foo" println mockOpenClass.message } } class MockOpenClass extends Expando { }
- MOP(Meta Object Protocol)
GroovyObject#setMetaClass(MetaClass)
class Main { static void main(String[] array) { GroovyObject groovyObject = new Main() Interceptor interceptor = new GreetingInterceptor() InterceptorUtils.setInterceptor(groovyObject, interceptor) groovyObject.greet() } } class InterceptorUtils { static void setInterceptor(GroovyObject groovyObject, Interceptor interceptor) { ProxyMetaClass proxyMetaClass = ProxyMetaClass.getInstance(groovyObject.getClass()) proxyMetaClass.setInterceptor(interceptor) groovyObject.setMetaClass(proxyMetaClass) } } class InterceptorImpl implements Interceptor { Object beforeInvoke(Object groovyExtensionObject, String name, Object[] arguments) { return null } Object afterInvoke(Object groovyExtensionObject, String name, Object[] arguments, Object beforeInvokeReturnObject) { Object object = invokeMethod(name, arguments) return object } boolean doInvoke() { return false } } class GreetingInterceptor extends InterceptorImpl { void greet() { println "Hello World" } }
- 予約語useのブロックによる、Groovyのカテゴリーで、Javaの標準クラスでも、オープンクラスに見えるコードが記述可能です。useブロック内のメソッドを起動するコードは、ブロックで指定したクラスのクラスメソッドを優先して、ディスパッチされます。
import groovy.inspect.Inspector class Main { static void main(String[] array) { use (Category.class) { Object object = "" println object.getShortClassName() println object.toString() } } } //名前は自由 class Category { //最初の引数は、メソッドが起動されたインスタンスの参照コピー static String getShortClassName(Object object) { Class clazz = object.getClass() String shortClassName = Inspector.shortName(clazz) return shortClassName } //実装メソッドと重複する場合、Groovyはカテゴリーを優先 static String toString(Object object) { return "Hello World" } }
[編集] 特徴的な言語機能
Groovyの特徴的な言語機能のいくつかを以下に示す。
[編集] GroovyMarkup
Groovyコードの表記を使い、Groovyの機能(クロージャやダイナミックなメソッド追加)を駆使してツリーデータ構造の組み上げを行う。具体的には、新規ノードの追加をメソッド呼び出しとして、その新規ノードの子ノード群の記述をメソッドに渡すクロージャとして定義する。そのクロージャにはさらにその子ノードのための一連のノード追加メソッド呼び出しを含めることができ…… というように再帰的に記述していく。このときGroovyのループ文やif文などの制御構造を含むすべてのGroovyの言語機能を使うことができる。
GroovyMarkupは直感的には、XMLほど静的ではないが、純粋なプログラムコード列よりは宣言的な、「やや宣言的なデータ記述」であるといえるかもしれない。
GroovyMarkupは基本的な機能であり、GroovyMarkupを使った具体的なライブラリとしては、SwingのGUIコンポーネントの組み立てを行うSwingBuilder、DOMのようなXMLデータ構造を組み立てるMarkupBuilderなどがある。
import groovy.xml.MarkupBuilder class Main { static void main(array) { Writer writer = new StringWriter() writer.println("<?xml version='1.0' ?>") writer.println() def builder = new MarkupBuilder(writer) /* 名前がルートのタグ名であるメソッド 引数がマップである場合はタグの属性 引数が文字列である場合はテキストノードの内容です。改行が無い場合はHTMLエスケープされます。 未実装メソッドをハンドルするGroovyObject#invokeMethod(String methodName, Object methodParameter)を利用 メソッドの括弧が省略されています。 */ builder.html(xmlns:"http://www.w3.org/1999/xhtml", "xml:lang":"ja") { //以降は名前がタグ名であるクロージャ /* 引数がクロージャである場合は名前がタグ名 引数がマップである場合はタグの属性 引数が文字列である場合はテキストノードの内容です。改行が無い場合はHTMLエスケープされます。 */ head() { } body() { div("1行目"); div("2行目"); //ヒア・ドキュメント構文 String string = """ <div id='3'>3行目</div> <div id='4'>4行目</div> """ p(string) { } } } println writer.toString() /* 標準出力結果 <?xml version='1.0' ?> <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='ja'> <head /> <body> <div>1行目</div> <div>2行目</div> <p> <div id='3'>3行目</div> <div id='4'>4行目</div> </p> </body> </html> */ } }
[編集] クロージャ
Groovyではコードブロックをファーストクラス(一級市民)オブジェクトとして生成し、変数に格納したりメソッド引数や戻り値として受け渡したりすることができる。Groovyのライブラリは繰り返し処理や入出力処理などを中心にクロージャが駆使されており、簡潔な表記を行うことができる。
クロージャは構築されたスコープ内の変数を読み書きできます。
class Main { static void main(String[] array) { String string = "Hello World" Closure readerClosure = {println string} readerClosure() Closure writerClosure = {string = "foo"} writerClosure() println string /* 標準出力結果 Hello World foo */ } }
[編集] Groovy JDK
GroovyからJ2SEの標準APIをすべて呼び出すことができるが、この際にGroovyから使うと便利なメソッドがリフレクションを用いてJ2SEクラスに擬似的に多数追加されている。例えば、クロージャをとるFile#eachLine()といったメソッドを使用することができ、以下の様な記述が可能となっている。
new File("test.txt").eachLine { println it }
また、例外発生の有無によらずに、File#eachLine(Closure)は処理終了時に、自動でファイルをクローズします。また、Reader#eachLine(Closure)メソッドが追加されています。そして、File#getText(String characterCodeSetName)、Reader#getText()、Reader#readLines()というファイルの全ての内容を一括で読み込むメソッドが追加されています。
[編集] 他の言語からの影響
James StrachanはGroovyはオブジェクト指向スクリプト言語Rubyから大きな影響を受けていることを何度か公言している。実際、クロージャの仕様や表記、その他予約語の選択などにおいてRubyからの影響を色濃く見ることができる。その他、Python、Dylan、Smalltalkなどからも言語機能が取り込まれている。
[編集] 適用分野
Groovyは開発中の言語でありバグや問題点も少なくないが、現状で有用な用途としては、Javaシステム開発におけるテストコードの記述を上げることができる(Groovyには標準でJUnit機能が組み込まれている)。もちろんスクリプト言語として、フィルタ的なツールをやプロトタイプを書き下すことも容易である。
アプリケーションの複雑なコンフィグレーションやカスタマイズ用の言語として用いるということも注目されている。Antの設定ファイル (build.xml) をGroovyで記述する機能は標準で組み込まれているし、いくつかのDIコンテナ(依存性注入コンテナ、IoCコンテナ)と呼ばれるアプリケーションフレームワークにおける起動時設定ファイルの記述言語として採用されるなど、XMLの代用として採用されはじめている。
将来的には、既存Javaシステムを連携させるグルー言語として、Microsoft Windowsの世界におけるVisual BasicやVBAの役割をJavaシステム全般において果たせる可能性がある。
[編集] 標準化
Groovyは2004年3月29日にJava技術の標準化プロセスJCPにおいてJSR 241として受理され仕様の標準化がすすめられている。[1] このことによってcodehausによる実装以外の実装が出てくる可能性が生まれている。また各社製品ベンダによってサポートが進められていくことが期待される。
[編集] サンプルコード
class Foo { doSomething() { Map data = ["name": "James", "location": "London"] for (e in data) { println("entry ${e.key} is ${e.value}") } } closureExample(collection) { collection.each { println("value ${it}") } } static void main(args) { List values = [1, 2, 3, "abc"] foo = new Foo() foo.closureExample(values) foo.doSomething() } }