Groovy

概要

GroovyはJVM上で動作する言語処理系および言語の名称であり、Javaとの直接的な連携を特徴とする。例えばGroovyからすべてのJava SE 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コードはクラス定義中にある必要はなく、クラス定義の外側でのメソッドの定義や実行文の記述が可能である。
以下、ファイル名が HelloTest.groovy とする
println "Hello, World!"
すると、
class HelloTest {
    public HelloTest() {
        println "Hello, World!"
    }
    public static void main(String[] args) {
        new HelloTest()
    }}
と同じ意味を持つ。

switch, case

switch/case文は任意の型に対して分岐することができるように拡張されている。
switch (value) {case "Hello": 
   println "value == 'Hello'"
   breakcase String: 
   println "valueはString型"
   breakcase 1..12: 
   println "valueは1～12の間"
   breakdefault:
   println "それ以外"}

for

通常の for と for in がある。for を使うと、break や continue が使える。@CompileStatic を付けた状態では、C言語スタイルの for ループかつループ変数に型を付けた状態が最速であり、Java言語と同等の速度で動く。each や times はクロージャ呼び出し分の時間がかかる。
for (int i = 0; i < 3; i++) { println "$i: Hello" }for (i in 1..3) { println "$i: Hello" }(1..3).each { println "$it: Hello" }3.times { println "$it: Hello" }

Getter, Setter

Getter、Setterメソッドは自動生成される。フィールドアクセスの記法でGetter、Setterメソッドを呼び出すことができる。
class Pojo {
    def name}def pojo = new Pojo(name:"名前")println pojo.getName() // getName()が生成されているprintln pojo.name // getName()が呼ばれる

デフォルト引数

デフォルト引数（メソッド・コンストラクタ呼び出しチェインの自動生成）。
def greet(mess = "Hello World") {
    println mess}greet()greet("foo")
は
Hello World
foo
と出力される。

ExpandoMetaClass

def obj = "foo"obj.metaClass.greet = { println "Hello World" }obj.greet()

Expando

Groovyは未実装のフィールドの参照と代入、未実装のメソッドの起動をキャッチしGroovyObjectのメソッドを起動する。
GroovyObject#getProperty(String name)GroovyObject#setProperty(String name, Object value)GroovyObject#invokeMethod(String name, Object arguments)
以下、Expando を使用した例である。
def obj = new Expando()obj.greetingMessage = "Hello World"obj.greet = { println greetingMessage }obj.greet()obj.message = "foo"println obj.message
また、連想配列を使用しても、似た構文が可能である。thisの意味が変わる。
def obj = [:]obj.greetingMessage = "Hello World"obj.greet = { println obj.greetingMessage }obj.greet()obj.message = "foo"println obj.message

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

未実装のメソッドをuseブロック内で起動すると、ブロックで指定したクラスのクラスメソッドに処理をディスパッチする。
import groovy.inspect.Inspectoruse (Category.class) {
    def obj = "Hoge"
    println obj.getShortClassName()
    println obj.toString()}//名前は自由class Category {
    //最初の引数は、メソッドが起動されたインスタンスの参照コピー
    static getShortClassName(obj) {
        Inspector.shortName(obj.getClass())
    }
    //実装メソッドと重複する場合、Groovyはカテゴリーより実装メソッドを優先
    static String toString(Object obj) {
        "Hello World"
    }}
は
String
Hoge
と出力される。

GroovyMarkup

Groovyコードの表記を使い、Groovyの機能（クロージャやダイナミックなメソッド追加）を駆使してツリーデータ構造の組み上げを行う。具体的には、新規ノードの追加をメソッド呼び出しとして、その新規ノードの子ノード群の記述をメソッドに渡すクロージャとして定義する。そのクロージャにはさらにその子ノードのための一連のノード追加メソッド呼び出しを含めることができ…… というように再帰的に記述していく。このときGroovyのループ文やif文などの制御構造を含むすべてのGroovyの言語機能を使うことができる。
GroovyMarkupは直感的には、XMLほど静的ではないが、純粋なプログラムコード列よりは宣言的な、「やや宣言的なデータ記述」であるといえるかもしれない。
GroovyMarkupは基本的な機能であり、GroovyMarkupを使った具体的なライブラリとしては、SwingのGUIコンポーネントの組み立てを行うSwingBuilder、DOMのようなXMLデータ構造を組み立てるMarkupBuilderなどがある。
import groovy.xml.MarkupBuilderclass 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>    """

        pre(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>    <pre>      &lt;div id='3'&gt;3行目&lt;/div&gt;      &lt;div id='4'&gt;4行目&lt;/div&gt;    </pre>  </body></html>    */
  }}

クロージャ

Groovyではコードブロックをファーストクラス（第一級）オブジェクトとして生成し、変数に格納したりメソッド引数や戻り値として受け渡したりすることができる。Groovyのライブラリは繰り返し処理や入出力処理などを中心にクロージャが駆使されており、簡潔な表記を行うことができる。
クロージャは構築されたスコープ内の変数を読み書きできる。
def str = "Hello World"def readerClosure = { println str }readerClosure()def writerClosure = { str = "foo" }writerClosure()println str
このコードを実行すると
Hello World
foo
と出力される。

Groovy JDK

GroovyからJava SEの標準APIをすべて呼び出すことができるが、この際にGroovyから使うと便利なメソッドがリフレクションを用いてJava SEクラスに擬似的に多数追加されている。例えば、クロージャをとるFile#eachLine()といったメソッドを使用することができ、以下の様な記述が可能となっている。
new File("test.txt").eachLine { println it }
File#eachLine(Closure)はそれぞれの行の値を変数itに代入してクロージャを呼び出す、という繰り返し処理を行うだけでなく、処理の終了時もしくは例外の発生時に、ファイルのクローズ処理を行う。つまりJavaの場合必要なfinally節におけるclose処理を必要とせず簡潔に記述できる。他に同様に、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の応用として注目すべき事例として、Grailsをあげることができる。GrailsはGroovyを使用したWebアプリケーションフレームワークであり、Webアプリ開発においてRuby on Railsが実現しているような高い生産性をもたらす。

標準化

Groovyは2004年3月29日にJava技術の標準化プロセスJCPにおいてJSR 241として受理され仕様の標準化がすすめられたが、その後 dormant (休止)扱いとなった。

クロージャとループ

def forLoop() {
    def map = [name: "James", location: "London"]
    for(e in map) { println "entry $e.key is $e.value" }}def closureExample(list) {
    list.each { println "value $it" }}def values = [1, 2, 3, "abc"]closureExample(values)forLoop()

メモ帳

import groovy.swing.SwingBuilderimport javax.swing.*def notepadnew SwingBuilder().frame(title: "メモ帳", defaultCloseOperation: JFrame.EXIT_ON_CLOSE,
    size: [800, 600], show: true, locationRelativeTo: null) {
  menuBar() {
    menu(text: "ファイル(F)", mnemonic: 'F') {
      menuItem(text: "名前をつけて保存(A)...", mnemonic: 'A', actionPerformed: {
        fc = new JFileChooser()
        if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
          fc.selectedFile.text = notepad.text
        }
      })
      menuItem(text: "終了(X)", mnemonic: 'X', actionPerformed: { System.exit(0) })
    }
  }
  scrollPane() { notepad = textArea() }}

統合開発環境

多くの統合開発環境（IDE）がGroovyに対応している。