BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Groovy 2.0の新機能

Groovy 2.0の新機能

ブックマーク

原文(投稿日:2012/06/28)へのリンク

新しくリリースされたGroovy 2.0は、この言語に静的型チェック静的コンパイルといった重要な静的機能を導入します。Project Coinのシンタックス拡張や新しいJVM命令"invoke dynamic"のサポートといったJDK 7対応の改良も取り入れられ、また、以前に比べよりモジュール化されています。 この記事ではこれらの新機能をより詳しく見ていきます。

動的な言語のための "静的なテーマ"

静的型チェック

Groovyは生来、そして将来にわたって常に動的な言語です。しかし、Groovyは "Java用スクリプト言語" や "ベターJava" (冗長な記述が少なく、より強力な機能を持つJava)として使われることがよくあります。実際、多くのJava開発者がより表現力のあるビジネスルールを書いたり、顧客ごとにアプリケーションをカスタマイズするための拡張用言語として、GroovyをJavaアプリケーションの中に組み込んで使っています。こういったJava指向のユースケースでは、開発者はこの言語が持つ動的な機能のすべてが必要なわけではありません。また開発者はGroovyのコンパイラにも、いつもjavacがくれるようなフィードバックを期待していることが普通です。とりわけ、変数名やメソッド名のタイプミス、間違った型への代入などには、実行時エラーではなくコンパイルエラーが期待されています。Groovy 2に静的型チェックのサポートを盛り込んだのはこういった理由からです。

タイプミスの検出

静的型チェッカーは、Groovyの既存の強力なAST(Abstract Syntax Tree, 抽象構文木)変換メカニズムを使って作られています。この仕組みをご存知ない方は、アノテーションをトリガーとして起動されるオプションのコンパイラプラグインだと考えてください。オプション機能ですので不要なら使う必要はありません。静的型チェックを起動するには、チェックを有効にしたい範囲に応じて、対象メソッドまたはクラスに@TypeCheckedアノテーションを付けるだけです。最初の例として、実際に使う様子を見てみましょう:

import groovy.transform.TypeChecked

void someMethod() {}

@TypeChecked
void test() {
    // コンパイルエラー:
    // マッチするsommeeMethod()メソッドが見つからない
    sommeeMethod()

    def name = "Marion"

    // コンパイルエラー:
    // 変数naaammmeが宣言されていない
    println naaammme
}

test()メソッドに@TypeCheckedアノテーションを付けました。 これは、コンパイル時に指定したメソッドの静的型チェックを実行するようGroovyコンパイラに指示します。 ここではsomeMethod()をわざとタイプミスして呼び出し、 さらにname変数もタイプミスして出力しようとしています。 指定したメソッドが存在しなかったり、変数が宣言されていなかったりするため、 コンパイラは2つのコンパイルエラーを報告するでしょう。

代入や戻り値のチェック

静的型チェッカーは、戻り型や、代入時の値の整合性も検証します:

import groovy.transform.TypeChecked

@TypeChecked
Date test() {
    // コンパイルエラー:
    // Date値はint型の変数には代入できない
    int object = new Date()

    String[] letters = ['a', 'b', 'c']
    // コンパイルエラー:
    // String値はDate型の変数には代入できない
    Date aDateVariable = letters[0]

    // コンパイルエラー:
    // Date型を返すメソッドでString値を返すことはできない
    return "today"
}

この例では、int変数にDateは代入できないことや、メソッドシグネーチャで指定されているDate値のかわりにStringは返せないことをコンパイラが指摘します。 スクリプトの中ほどのコンパイルエラーにも注目してください。これは間違った代入が検出されるということだけでなく、型推論が行われていることも示しています。 型チェッカーは、String配列を処理しているのだから当然letters[0]String型である、ということを知っているのです。

型推論についてもう少し

型推論に触れたので、他の事例についても見てみましょう。型チェッカーが戻り型と値を検証することは述べました:

import groovy.transform.TypeChecked

@TypeChecked
int method() {
    if (true) {
        // コンパイルエラー:
        // int型を返すメソッドでString値を返すことはできない
        'String'
    } else {
        42
    }
}

プリミティブ型のint値を返すメソッドを考えます。型チェッカーは、if / else分岐、try / catchswitch / caseブロックといったさまざまな構造からの戻り値もチェックできます。例えばこの例では、if / elseブロックの分岐の一方で、プリミティブのint型ではなくString値を返そうとし、コンパイラがそのことを検出しています。

便利な型変換はそのまま使用可能

静的型チェッカーは、Groovyがサポートしているある種の自動型変換については文句を言いません。 例えば、StringbooleanClassを返すメソッドでは、Groovyは戻り値をこれらの型に自動変換します:

import groovy.transform.TypeChecked

@TypeChecked
boolean booleanMethod() {
    "空でない文字列はtrueと評価される"
}

assert booleanMethod() == true

@TypeChecked
String stringMethod() {
    // StringBuilderはtoString()でStringに変換される
    new StringBuilder() << "non empty string"
}

assert stringMethod() instanceof String

@TypeChecked
Class classMethod() {
    // java.util.Listクラスが返される
    "java.util.List"
}

assert classMethod() == List

静的型チェッカーは賢く、次のような型推論も行います:

import groovy.transform.TypeChecked

@TypeChecked
void method() {
    def name = " Guillaume "

    // String型と推論(GStringの中でも)
    println "NAME = ${name.toUpperCase()}"

    // Groovy GDKのメソッドにも対応
    // (GDKの演算子オーバーロードも)
    println name.trim()

    int[] numbers = [1, 2, 3]
    // 要素nはint
    for (int n in numbers) {
        println
    }
}

name変数はdefで定義されていますが、型チェッカーはそれがString型であることを理解しています。そしてこの変数が挿入文字列の中で使われている部分ではStringtoUpperCase()メソッドを、その次の行ではGDKがStringクラスに追加しているtrim()メソッドを呼べばいいことを知っています。最後に、プリミティブintの配列の要素をイテレートするところでは、この配列の要素がintであることももちろん理解しています。

動的な機能と静的に型付けたメソッドの併用

覚えておくべき重要なポイントは、静的型チェックの使用はGroovyで利用可能な機能を制限するということです。コンパイル時に静的に型がチェックできないので、実行時の動的機能の大部分は使えません。したがって、メタクラスを使って実行時に新しいメソッドを追加することはできません。しかし、ある特定の動的機能(例えばGroovyのビルダーのような)を使う必要がある場合、静的型チェックを使わないことはあなたの自由です。

@TypeCheckedアノテーションは、クラスレベルまたはメソッドレベルで付けることができます。クラス全体で型チェックをしたければクラスにアノテーションを付け、いくつかのメソッドだけで型チェックをしたければそのメソッドだけにアノテーションを付ければよいのです。また、ある特定のメソッドだけ型チェックから除外したい場合には、そのメソッドに@TypeChecked(TypeCheckingMode.SKIP)アノテーション(関連enumをstatic importしておけば、短く@TypeChecked(SKIP)でも可)を付けることもできます。以下のスクリプトで、greeting()メソッドは型チェックし、generateMarkup()メソッドは型チェックしない状況を説明しましょう:

import groovy.transform.TypeChecked
import groovy.xml.MarkupBuilder

// このメソッドと内部のコードは型チェックされる
@TypeChecked
String greeting(String name) {
    generateMarkup(name.toUpperCase())
}

// このメソッドは型チェックされないので
// markup builderのような動的機能を使える
String generateMarkup(String name) {
    def sw =new StringWriter()
    new MarkupBuilder(sw).html {
        body {
            div name
        }
    }
    sw.toString()
}

assert greeting("Cédric").contains("<div>CÉDRIC</div>")

型推論とinstanceofチェック

現在の製品版のJavaは一般的な型推論をサポートしていません。そのため、コードが非常に冗長だったり定型的な記述であふれていることもしばしばです。これによってコードの意図が不明確になったり、強力なIDEなしではコーディングが困難になったりしています。instanceofチェックもこの一例です。ifの条件節でinstanceofを使ってある値のクラスを確認した直後のブロックであっても、その値のメソッドを使うためには依然としてキャストが必要なのです。Groovyでは、通常モードでも新しい静的型チェックモードでも、こういったキャストは完全に排除することができます。

import groovy.transform.TypeChecked
import groovy.xml.MarkupBuilder

@TypeChecked
String test(Object val) {
    if (val instanceof String) {
        // Javaでは次のように書く必要がある:
        // return ((String)val).toUpperCase()
        val.toUpperCase()
    } else if (val instanceof Number) {
        // Javaでは次のように書く必要がある:
        // return ((Number)val).intValue().multiply(2)
        val.intValue() * 2
    }
}

assert test('abc') == 'ABC'
assert test(123) == '246'

上の例で、静的型チェッカーは最初のifブロック内のvalパラメータはString型であり、 次のifブロック内ではNumber型であることを知っているのです(キャストは一切不要)。

最小上界(Lowest Upper Bound)

静的型チェッカーの型推論は、オブジェクトの型についてのきめ細やかな理解という点ではさらにもう一歩先に進んでいます。以下のコードを考えてみましょう:

import groovy.transform.TypeChecked

// 推論される戻り型:
// ComparableかつSerializableなNumberのリスト
@TypeChecked test() {
    // IntegerとBigDecimal
    return [1234, 3.14]
}

この例ではIntegerBigDecimalということで、直感通りNumberのリストが返されます。しかし、静的型チェッカーはさらに「最小上界(Lowest Upper Bound)*訳注」と呼ばれるものを計算し、それはSerializableかつComparableであるNumberのリストになります。この型を標準的なJavaの型記法で表現することはできませんが、もし共通集合を「&」記号で表せるとしたら、List<Number & Serializable & Comparable>という感じになるでしょう。

*訳注:ここでは型インタフェースの公約数のようなものだと考えるとわかりやすいと思います。

フロータイピング

良いプラクティスとしておすすめできるものではありませんが、ときどき開発者は型付けしていない変数を使いまわして、異なる型の値を格納することがあります。以下のメソッドの中を見てください:

import groovy.transform.TypeChecked

@TypeChecked test() {
    def var = 123             // int型と推論
    var = "123"               // varにStringを代入

    println var.toInteger()   // 問題なし、キャスト不要

    var = 123
    println var.toUpperCase() // エラー!varはint型
}

var変数はintで初期化され、その後Stringが代入されています。「フロータイピング」アルゴリズムはこの代入の流れ(フロー)を追跡し、この変数が現在Stringを格納していることを理解しているので、GroovyがStringに追加したtoInteger()メソッドを呼び出しても静的型チェッカーは何も問題を検出しません。次に、var変数に再び数値が代入されます。その後toUpperCase()が呼ばれると、IntegerにはtoUpperCase()メソッドがないので、型チェッカーがコンパイルエラーを出力します。

フロータイピングアルゴリズムにはいくつか特別なケースがあります。変数がクロージャと共有される場合です。ローカル変数が定義されたメソッド内で、クロージャの中からその変数を参照したとき一体何が起こるでしょうか?この例を見てください:

import groovy.transform.TypeChecked

@TypeChecked test() {
    def var = "abc"
    def cl = {
        if (new Random().nextBoolean()) var = new Date()
    }
    cl()
    var.toUpperCase() // コンパイルエラー!
}

varローカル変数にはStringが代入されています。しかしその後、乱数の値によってはvarDateが代入されるかもしれません。通常、このクロージャ内のif文の条件が満たされるかどうかは実行時にしかわかりません。したがってコンパイルの時点で、varが今Stringを格納しているのかDateを格納しているのかをコンパイラが知るすべはありません。これがコンパイラがtoUpperCase()の呼び出しに文句を言う理由です。変数がStringを格納しているかどうかを推論できないのです。この例はちょっと不自然でしたが、もっと興味深いケースもあります:

import groovy.transform.TypeChecked

class A           { void foo() {} }
class B extends A { void bar() {} }

@TypeChecked test() {
    def var = new A()
    def cl = { var = new B() }
    cl()
    // いずれにせよvarはAのインスタンスなのでfoo()メソッドの呼び出しはOK
    var.foo()
}

上のtest()メソッドでは、varにまずAのインスタンスが代入され、次に(後で呼ばれる)クロージャ内でBのインスタンスが代入されています。そこで、varは少なくともA型であると推論できます。

Groovyコンパイラに追加されたこれらのチェックはすべてコンパイル時に行われるものです。しかし、生成されるバイトコードは以前の動的なコードとまったく同じです。その振る舞いは一切変わっていません。

コンパイラがプログラムの型情報についてより詳しくなったことで、面白い可能性も広がります:この型チェックされたコードを静的にコンパイルしたらどうでしょう?明らかな利点は、生成されるバイトコードをjavacコンパイラが生成するバイトコードに近づけることで、静的にコンパイルされたGroovyコードをJava並みの速さにできることでしょう。次の節では、Groovyの静的コンパイルについて見ていきます。

静的コンパイル

後のJDK 7に関する章で見るように、Groovy 2.0は新しい "invoke dynamic" JVM命令および関連APIをサポートします。これはJavaプラットフォーム上での動的言語の開発を助けるもので、Groovyの動的呼び出しのパフォーマンスをさらに改善します。しかし残念ながらこの記事の執筆時点では、JDK 7は実業務ではまだそう広く使われているとは言えません。誰もが最新版を使う機会があるわけではないのです。JDK 7上で実行できないのなら、パフォーマンス向上を求めている開発者にとって、Groovy 2.0では大した変化は見られないかもしれません。幸運にも、Groovy開発チームはこういった開発者も興味深いパフォーマンスの改善を得られるように、型チェックされたコードを静的にコンパイルできるようにしました。

さて、これ以上とやかく言うのはやめて、さっそく新しい@CompileStatic変換を使ってみましょう:

import groovy.transform.CompileStatic

@CompileStatic
int squarePlusOne(int num) {
    num * num + 1
}

assert squarePlusOne(3) == 10

ここでは@TypeCheckedの替わりに@CompileStaticを使ってコードを静的にコンパイルすることで、 生成されるバイトコードはjavacが生成するバイトコードのようになり、高速に実行されます。 @CompileStatic@TypeCheckedアノテーションのようにクラスとメソッドに付けることができます。 @CompileStatic(SKIP)を使えば、クラスが@CompileStaticでマークされていても特定のメソッドの静的コンパイルをバイパスすることができます。

Java風のバイトコード生成のもう1つの利点は、アノテートしたメソッドのバイトコードサイズが、通常、動的メソッドのためにGroovyが生成するバイトコードより小さくなることです。 これは、動的なケースのバイトコードには、Groovyの動的機能をサポートするためのGroovyランタイムシステムを呼び出す余分な命令が含まれるからです。

最後に大事なことですが、静的コンパイルは、 フレームワークやライブラリの開発者がコードのあちこちで動的メタプログラミングを使っている場合に、 その悪影響を避けるために使うこともできます。 Groovyのような言語で利用できる動的機能は、開発者にとてつもないパワーと柔軟性を与えます。 しかし、どのメタプログラミング機能が使われているのか、システムの別の部分には別の前提が存在する可能性もあり、 注意を怠ればこれが意図しない結果をもたらすこともあります。 少しわざとらしい例ですが、2つの異なるライブラリを利用していて、 それぞれが同名だが実装が異なるメソッドを同じクラスに追加したらどうなるでしょうか? どんな振る舞いが予想されるでしょう? 動的言語の経験を持つユーザなら、たぶんこの問題は以前に見たことがあり、 これが「モンキーパッチ」と呼ばれていることもおそらくご存知でしょう。 コードベースの動的機能が不要な部分を静的コンパイルできることは、 あなたをモンキーパッチの影響から守ってくれます。 静的コンパイルされたコードはGroovyの動的ランタイムシステムを経由しないからです。 なお、この言語の動的な実行時機能は静的コンパイルのコンテキストでは使えませんが、 通常のAST変換メカニズムはすべて今まで通り使用可能です。 なぜなら、大部分のAST変換はコンパイル時に処理されるからです。

パフォーマンスについて言えば、Groovyの静的コンパイルされたコードは通常、javacのものとほぼ同程度の速さです。開発チームが使ったいくつかのマイクロベンチマークでは、多くのケースでJavaと完全に同等なパフォーマンスを示し、ときどき少し遅いことがある程度でした。

これまでは、パフォーマンスをさらに上げたい場合には、ホットスポット部分をJavaで書き直すよう開発者にアドバイスしてきました(JavaとGroovyが透過的かつシームレスに統合されているおかげです)。しかし静的コンパイルオプションを得た今、これはもう当てはまりません。プロジェクト全体をGroovyで開発したいと願っている人たちは、実際にそれが可能なのです。

Java 7およびJDK 7関連テーマ

Groovy言語の文法はJavaの文法がベースになっています。さらにGroovyは気の利いたショートカットを追加して開発者の生産性を高めています。Javaとのシンタックスの類似性は、その学習の容易さから常にGroovyのセールスポイントであり、幅広い採用の原動力にもなっています。そしてもちろん、GroovyのユーザはJava 7の"Project Coin"で追加されたシンタックスの改良の恩恵を受けることも期待しているでしょう。

またシンタックス面だけにとどまらず、JDK 7では目新しい新APIや、 動的言語の実装者の開発を助けパフォーマンスの向上に貢献する"invoke dynamic"という久しぶりの新バイトコード命令なども導入されています。

Project Coinの拡張構文

開発当初から(2003年からです!)GroovyはJavaを拡張したシンタックスや機能を持っていました。例えばクロージャや、さまざまな型が使えるswitch / case文などが思い浮かぶでしょう(Java 7ではStringだけが追加で使えるようになりました)。このswitchでのStringのようなProject Coinのシンタックス拡張のいくつかは、Groovyにはすでに備わっていたわけです。しかし、バイナリリテラルや数値リテラル中のアンダースコア、マルチキャッチブロックなど、拡張のいくつかは新しいもので、Groovy 2はこれらもサポートします。Project Coin拡張からの唯一の漏れは "try with resources" 構文ですが、これについては、GroovyではGroovy Development Kitの豊かなAPIを通してすでにさまざまな代替手段が提供されています。

バイナリリテラル

Java 6以前では、数値は10進か8進、または16進で表記しました(Groovyでも同じ)。Java 7とGroovy 2では、 "0b" 接頭辞を付けたバイナリ(2進)表記が使えます:

int x = 0b10101111
assert x == 175

byte aByte = 0b00100001
assert aByte == 33

int anInt = 0b1010000101000101
assert anInt == 41285

数値リテラル中のアンダースコア

長い数値リテラルを書く場合、数字のグルーピング(3桁ごと、ワード単位など)がわかりにくくなります。数値リテラルの中にアンダースコアを置けるようになったことで、こういったグループを見分けやすくなります:

long creditCardNumber = 1234_5678_9012_3456L
long socialSecurityNumbers = 999_99_9999L
double monetaryAmount = 12_345_132.12
long hexBytes = 0xFF_EC_DE_5E
long hexWords = 0xFFEC_DE5E
long maxLong = 0x7fff_ffff_ffff_ffffL
long alsoMaxLong = 9_223_372_036_854_775_807L
long bytes = 0b11010010_01101001_10010100_10010010

マルチキャッチブロック

例外をキャッチするとき、複数の例外を同じように処理したければcatchブロックを複製することになります。この回避策としては、共通処理を別メソッドに切り出すか、あるいはExceptionや最悪Throwableといったレベルで全部まとめてキャッチする、というより醜い方法をとることになります。マルチキャッチブロックを使えば、複数の例外をまとめてキャッチし、同じcatchブロックで処理するよう定義できます:

try {
    /* ... */
} catch(IOException | NullPointerException e) {
    /* 1つのブロックで2つの例外を処理する */
}

Invoke Dynamicサポート

この記事の前半で述べたように、JDK 7では "invoke dynamic" と呼ばれる新しいバイトコード命令がその関連APIとともに導入されました。この狙いは、動的言語の実装者たちがJavaプラットフォーム上に彼らの言語を構築する仕事を助けることです。動的メソッド呼び出しをキャッシュできる "call sites"、メソッドポインタとしての "method handles"、クラスオブジェクトのさまざまなメタデータを保管する "class values" などを定義することで、動的メソッド呼び出しを簡潔に書くことができるのです。注意すべき点は、 "invoke dynamic" はパフォーマンス改善に将来有望ではあるものの、まだJVM内で完全には最適化されていないということです。現時点では、必ずしも常に最高のパフォーマンスをもたらしてくれるわけではないのです。しかし今後アップデートを重ねれば、最適化は確実に進むでしょう。

Groovyには、「call siteキャッシュ」によってメソッドの選択と起動を高速化したり、メタクラスレジストリによってメタクラス(動的実行時におけるクラスに相当)を管理したり、Java並の速さでネイティブのプリミティブ演算を実行したり、といったさまざまな独自の実装テクニックが導入されてきました。しかしこの "invoke dynamic" の登場により、パフォーマンスの向上やコードベースをシンプルにするために、Groovyの実装をこのJVMバイトコード命令とAPIの上に構築し直すことができるようになりました。

運良くJDK 7を使える場合には、"invoke dynamic" のサポート付きでコンパイルされた、 Groovyの一連のJARファイルの新バージョンを使うことができます。 これらのJARは名前に "-indy" が付いているので簡単に識別できます。

invoke dynamicサポートの有効化

"invoke dynamic" サポートを活用できるようにGroovyコードをコンパイルするには、"indy" 付きのJARを使うだけでは不十分です。そのためには、"groovyc" コンパイラや "groovy" コマンドを使うとき、--indyフラグを指定する必要があります。これは、もしindy JARを使っていても(--indyを指定しなければ)JDK 5や6をコンパイルターゲットにできるということも意味しています。

同じように、プロジェクトのコンパイルにgroovyc Antタスクを使う場合、indy属性を指定することができます:

...
<taskdef name="groovyc"
        classname="org.codehaus.groovy.ant.Groovyc"
        classpathref="cp"/>
...
<groovyc srcdir="${srcDir}" destdir="${destDir}" indy="true">
    <classpath>
...
    </classpath>
</groovyc>
...

Groovy EclipseのMavenコンパイラプラグインは現在まだGroovy 2.0に対応していませんが、近日中には対応する予定です。GMavenプラグインを利用している場合、Groovy 2.0を使うようにプラグインを設定することは今でも可能ですが、invoke dynamicサポートを有効にするフラグはまだ存在していません。しかし先ほどと同様、GMavenもすぐにアップデートされるでしょう。

例えばGroovyShellを使ってGroovyをJavaアプリケーションに統合する場合、 GroovyShellのコンストラクタにCompilerConfigurationインスタンスを渡して最適化オプションを設定することで、invoke dynamicサポートを有効にすることができます:

CompilerConfiguration config = new CompilerConfiguration();
config.getOptimizationOptions().put("indy", true);
config.getOptimizationOptions().put("int", false);
GroovyShell shell = new GroovyShell(config);

invokedynamicは動的メソッドディスパッチを完全に置き換えることになっているので、 エッジケースの最適化のために余計なバイトコードを生成するプリミティブ最適化機能をオフにすることも必要です。 現時点ではプリミティブ最適化の方が速い場合もありますが、 JVMの将来のバージョンはメソッド呼び出しの多くをインライン化し不要なボクシングを排除できる、 改良されたJITを持つでしょう。

パフォーマンス改善への期待

私たちのテストによると、いくつかの領域で興味深いパフォーマンスの向上が見られる反面、 invoke dynamicサポートを使わない方が速いプログラムもあることがわかりました。 GroovyチームはGroovy 2.1に向けてさらなるパフォーマンス改善を続けますが、 JVM自体の調整がまだ不十分なため、完全な最適化にはまだ長い道のりがあることに気づいています。 幸運にも、今後のJDK 7のアップデート(特にアップデート8)にはさっそくこういった改良が含まれるはずなので、状況は良くなる一方です。 また、invoke dynamicはJDK 8のLambdaの実装にも使われるので、 さらなる改良が確実に期待できます。

よりモジュール化されたGroovy

最後にモジュール化について説明して、Groovy 2.0の新機能の旅を終わりたいと思います。Javaと同じように、Groovyも単なる言語ではありません。テンプレート、Swing UI構築、Antスクリプト、JMX統合、SQLアクセス、Servletなどなど、さまざまな用途のAPIの集合体でもあります。Groovyの配布形態は、こういったすべての機能とAPIを1つの大きなJARに同梱していました。しかし、すべてのアプリケーションがこのすべての機能を常に必要としているわけではありません。Webアプリケーションを書いているときはテンプレートエンジンとServletに興味があるかもしれませんが、デスクトップクライアントプログラムを開発しているときはSwingビルダーしか必要ないのかもしれないのです。

Groovyモジュール

このリリースでのモジュール化に関する最初の目標は、従来のGroovyのJARを、より小さなモジュール、より小さなJARに分割することです。コアのGroovy JARは約半分の大きさになり、以下の機能モジュールが用意されました:

  • Ant: 管理作業を自動化するためのAntタスクスクリプティング
  • BSF: 古いApache Bean Scripting Frameworkを使ってGroovyをJavaアプリケーションに統合
  • Console: GroovyのSwingコンソールを含むモジュール
  • GroovyDoc: GroovyおよびJavaクラスのドキュメント生成
  • Groovysh: Groovyshコマンドラインシェルに対応するモジュール
  • JMX: JMX Beanの発行と利用
  • JSON: JSONペイロードの生成と利用
  • JSR-223: JDK 6+のjavax.scripting APIを使ってGroovyをJavaアプリケーションに統合
  • Servlet: GroovyスクリプトによるServletとテンプレートの開発と運用
  • SQL: リレーショナルDBの問い合わせ
  • Swing: Swing UIの構築
  • Templates: テンプレートエンジンの使用
  • Test: GroovyTestCaseやモックなどのテストサポート
  • TestNG: GroovyによるTestNG用テストの開発
  • XML: XML文書の生成と利用

Groovy 2では、すべてをクラスパスに入れるのではなく、必要なモジュールだけを選んで使うことができます。ただ、ほんの数メガバイトの容量の節約のために依存関係を複雑にしたくないという方のために、すべてを含んだ "all" JARもまだ提供しています。また、JDK 7を使う方のために "invoke dynamic" サポート付きでコンパイルされたJARも提供しています。

拡張モジュール

Groovyのモジュール化を進める作業は、拡張モジュールという面白い新機能も生み出しました。Groovyをより小さなモジュールに分割していく過程で、モジュールで拡張メソッドを追加する仕組みが作られたのです。これによって拡張モジュールは、JDKやサードパーティのものを含む他のクラスにインスタンスメソッドやstaticメソッドを提供できます。GroovyはJDKのクラスを拡張するのにこの仕組みを使い、StringFile、各種ストリームなどのクラスに便利なメソッドを追加しています。例えば、URLクラスのgetText()メソッドを使えば、リモートのURLのコンテンツをHTTP getで取得できます。このモジュールによる拡張メソッドは、静的型チェッカーやコンパイラにも認識されることに注目してください。では、既存の型に新しいメソッドを追加する方法を見ていきましょう。

インスタンスメソッドの追加

既存の型に新しいメソッドを追加するためには、まずそのメソッドを含んだヘルパークラスを作る必要があります。ヘルパークラスの中のすべての拡張メソッドは、public(Groovyではデフォルトですが、Javaで実装する場合は明示が必要)、かつstatic(利用されるのは対象クラスのインスタンスですが)にします。これらのメソッドは必ず1個以上のパラメータをとり、最初の1つは実際にメソッドが呼ばれる対象のインスタンスです。続くパラメータは、そのメソッドが呼ばれるときに実際に渡されるパラメータになります。これはGroovyのカテゴリと同じ規約です。

例えばStringに、パラメータで指定した人に挨拶するgreets()メソッドを追加したいとします。このメソッドは次のように使えるでしょう:

assert "Guillaume".greets("Paul") == "Hi Paul, I'm Guillaume"

これを実現するためには、以下のような拡張メソッドを持つヘルパークラスを作ります:

package com.acme

class MyExtension {
    static String greets(String self, String name) {
        "Hi ${name}, I'm ${self}"
    }
}

staticメソッドの追加

staticな拡張メソッドの場合も仕組みや規約は同じです。 Randomに、2つの値の間をとるランダムな整数を得る、新しいstaticメソッドを追加してみます。次のクラスのように書けるでしょう:

package com.acme

class MyStaticExtension {
    static int between(Random selfType, int start, int end) {
        new Random().nextInt(end - start + 1) + start
    }
}

これで、以下のように拡張メソッドを使うことができます:

Random.between(3, 4)

拡張モジュールディスクリプタ

拡張メソッドを含んだヘルパークラス(GroovyでもJavaでもよい)を書き終えたら、次にモジュールのディスクリプタを作る必要があります。モジュールアーカイブのMETA-INF/servicesディレクトリの中にorg.codehaus.groovy.runtime.ExtensionModuleという名前のファイルを作ります。モジュールの名前、バージョン、拡張メソッドを持つヘルパークラスの名前をカンマ区切りで並べたリスト(インスタンスメソッド分とstaticメソッド分)、以上4種類の必須フィールドをGroovyランタイムに伝えるために定義できます。最終的なモジュールディスクリプタは次のようになります:

moduleName = MyExtension
moduleVersion = 1.0
extensionClasses = com.acme.MyExtension
staticExtensionClasses = com.acme.MyStaticExtension

この拡張モジュールディスクリプタを含むモジュールがクラスパス上にあれば、 拡張メソッドが自動的に登録され、 importなどの手続き一切なしで自分のコード内で拡張メソッドを使うことができるようになります。

拡張モジュールのGrab

スクリプト内で@Grabアノテーションを使えば、Maven CentralのようなMavenリポジトリから依存モジュールを取得できます。@GrabResolverアノテーションを追加して、モジュール探索のための独自ロケーションを指定することも可能です。この仕組みを使って拡張モジュールを "grab" する場合、拡張メソッドのインストールも自動的に行われます。理想的には整合性のため、モジュール名とバージョンはアーティファクトのartifact id、versionと一致しているべきです。

まとめ

GroovyはJava開発者の間でとても人気があり、アプリケーションのニーズにこたえて成熟したプラットフォームとエコシステムを提供しています。Javaプラットフォームでの開発効率をさらに向上させるべく、Groovy開発チームは言語やAPIの改良を休みなく続けています。

Groovy 2.0は3つの重要なテーマに対応しています:

  • パフォーマンスの向上: 業務でJDK 7を使える幸運な開発者にはInvoke Dynamicのサポートによる高速化を、そしてJDK 5以降を使うすべての開発者に静的コンパイルを提供。動的機能の一部をあきらめれば「モンキーパッチ」の影響を防いだり、Javaと同レベルのスピードを得ることも可能に。
  • Java親和性の向上: Java 7のProject Coin拡張のサポートにより、これまで通りGroovyとJavaのシンタックス類似性を維持。静的型チェッカーにより、GroovyをJava用スクリプト言語として使う開発者にjavacと同レベルのフィードバックと型安全性を提供。
  • モジュール性の向上: 新しいレベルのモジュール性により、より小さな配布形態を実現。 例えばAndroid用のモバイルアプリケーションへの統合、新しいバージョンや新しい拡張モジュールによるGroovy APIの成長と進化、そしてユーザによる既存の型への拡張メソッドの追加などが可能に。

著者について

VMWareの一部門、SpringSourceのGroovy開発リーダー、Guillaume Laforge氏はGroovyの公式なプロジェクトマネージャであり、CodehausでのGroovy動的言語プロジェクトを指揮しています。 

WebアプリケーションフレームワークGrailsの開発立ち上げにも携わり、Google App Engine用のGroovyアプリケーション開発のための軽量ツールキット、Gaelykプロジェクトも立ち上げました。また、JavaOne、GR8Conf、SpringOne2GX、QCon、Devoxxなどのカンファレンスで、GroovyやGrails、Gaelyk、DSL(Domain Specific Language)といったテーマで数多くのプレゼンテーションを行っています。 

GuillaumeはフランスのJava/OSS/ITポッドキャスト、LesCastCodeursの創立メンバの一人でもあります。

この記事に星をつける

おすすめ度
スタイル

BT