本格派エンジニアの工具箱

第24回クロージャや拡張メソッドでJava開発をサポートする「Eclipse Xtend」

まずはお約束のHelloWorld

前回は、Eclipseプロジェクトによって開発された新プログラミング言語Eclipse Xtendの概要と、Eclipseへのインストール方法を解説しました。今回は、Xtendの特徴的な機能のうちのいくつかを紹介します。

まずはその前に、お約束のHelloWorldプログラムを作ってみましょう。Xtendを使う場合には、通常のJavaアプリケーションと同じようにプロジェクトを作成し、ライブラリとして関連するjarファイルを追加します。jarファイルはEclipseのプラグインフォルダ(EclipseがC:\eclipseにインストールされている場合には、C:\eclipse\plugins)にあります。基本的な機能であれば「org.eclipse.xtext.xtend2.lib_xxxx.jar」「org.eclipse.xtext.xbase.lib_xxxx.jar」⁠xxxxはバージョン番号)があればOKです。

※注:Eclipse Xtend 2,2がリリースされました。コンストラクタやstaticメソッドは2.2以降でしかサポートされないので、2.1以前をインストールしている場合は必ずアップデートしておきましょう。

プログラムのソースファイルも、Javaと同様にsrcフォルダにパッケージを作成して配置します。新規ファイル作成ウィザードで[Xtend]-[Xtend Class]を選択すれば、クラスファイルの雛形を作成することができます。ファイルの拡張子は.xtendです。

図1 新規ファイル作成ウィザードでXtend用クラスの雛形が作成できる
図1 新規ファイル作成ウィザードでXtend用クラスの雛形が作成できる

HelloWorld.xtendの例を以下に示します。

HelloWorld.xtend
package jp.gihyo.toolbox.xtend

class HelloWorld {
        def String sayHello(String name) {
                var hello =  "Hello "+name+"!"
                hello
        }

        def static void main(String[] args) {
                val HelloWorld hello = new HelloWorld()
                println(hello.sayHello("Gihyo"))
        }
}

Javaとの大きな違いは、行末にセミコロンが付かない点です。メソッドの定義には「def」を、変数宣言には「var」または「val」を使用します。valの場合は変更負荷の変数になります。変数の型宣言は省略することができますが、これは型推論によってコンパイル時に自動で型付けされるだけで、Xtendはあくまでも静的型付けの言語です。メソッドの戻り値は最後に評価した式の値になるので、⁠return」キーワードは省略することができます。

main()メソッドはJavaと同様にstatic修飾子を付けて作成します。実行方法もJavaプログラムと同様で、ファイル名を右クリックして[実行]-[Javaアプリケーション]を選択すれば実行できます。

このプログラムはJavaコードにコンパイルされ、xtend-genフォルダ以下に「HelloWorld.java」が作成されます。これは通常のJavaファイルとまったく変わらないものなので、Javaコードから呼び出すこともできます。

クロージャ

Xtendではクロージャを使うことができます。クロージャを作成するための文法は『[ 引数リスト | 処理内容 ]』の形で定義し、apply()メソッドによって実行します。次のコードは、文字列を引数として受け取り、その内容を表示するクロージャの例です。apply()に渡された文字列(この場合は「Hello!⁠⁠)が出力されます。

val func = [String s | println(s)]
func.apply("Hello!")           // "Hello!"を出力

クロージャを引数にとるメソッドや、戻り値としてクロージャを返すメソッドを作ることもできます。クロージャの型は『(引数の型のリスト) => 文字列の型』のように記述します。次のmyFunc1()メソッドは、第2引数としてクロージャを受け取るメソッドの例です。第1引数に渡されたリストの各要素に対して、それぞれクロージャの処理を実行します。

def myFunc1(ArrayList<String> names, (String) => String function){
    val result = newArrayList()
    for (n : names) {
        result += function.apply(n)
    }
    return result
}

myFunc1()を実行するコードは次のようになります。namesに対するメソッド呼び出しのように見えますが、実際にはnamesがmyFunc1()の第1引数として扱われます。これは後述する拡張メソッドの機能によるものです。1つ目の呼び出しではtoUpperCase()、2つ目の呼び出しではtoLowerCase()を実行するクロージャを渡しているので、実行すればそれぞれ「[TARO, JIRO]⁠⁠、⁠[taro, jiro]」が出力されます。

var names = new ArrayList<String>() 
names.add("Taro")
names.add("Jiro")
println(names.myFunc1(n | n.toUpperCase()))    // "[TARO, JIRO]"を出力
println(names.myFunc1(n | n.toLowerCase()))    // "[taro, jiro]"を出力

次の例は戻り値としてクロージャを返すメソッドmyFunc2()を定義したものです。引数を文字列にとり、⁠"Hello " + name + "!"』を実行するクロージャが返されます。

def (String) => String myFunc2(){
    return [String name | "Hello " + name + "!"]
}

myFunc2()から返されたクロージャは、通常通りapply()メソッドを呼び出すことで実行できます。

println(myFunc2().apply("Gihyo"))    // "Hello Gihyo!"を出力

拡張メソッド

Xtendには拡張メソッドと呼ばれる機能が用意されています。これは、独自に定義したメソッドをあたかも既存のクラスのメソッドであるかのように呼び出すことができる仕組みです。次のに示す例は、hello()をStringの拡張メソッドとして定義したものです。

def hello(String name) {
    println("Hello " + name + "!")
}

このhello()メソッドは、次のようにあたかも第1引数の型であるStringクラスのメソッドであるかのように呼び出すことができます。

"Gihyo".hello()     // "Hello Gihyo!"を出力

もちろん、これは実際にStringにhelloメソッドが追加されたわけではなく、内部では『this.hello("Gihyo")』の呼び出しとして扱われます。前述のクロージャの例でも、この拡張メソッドの仕組みを利用して、第1引数の型を呼び出し元のレシーバであるかのように使っています。

プロパティへのアクセス

Xtendのオブジェクトがもつプロパティに対しては、⁠変数名.プロパティ名』という形式で、アクセッサメソッド経由のアクセスを行うことができます。例えば、次のような2つのプロパティと、それに対するアクセッサメソッド、そして面積を計算するgetArea()メソッドを持つクラスを考えてみます。

class Square {
    private int width
    private int height
        
    def setWidth(int width) {
        this.width = width
    }
    def getWidth() {
        return this.width
    }
        
    def setHeight(int height) {
        this.height = height
    }
    def getHeight() {
        return this.height
    }
        
    def getArea() {
        return this.width * this.height
    }
}

このSquareクラスのインスタンスsquareを作った場合、widthとheightという2つのプロパティには、それぞれ『square.width⁠⁠、⁠square.height』の形式でアクセスできるということです。Javaコードにコンパイルされる際には、このアクセスはsetWidth()やgetWidth()を経由したものに置き換えられます。以下に使用例を示します。

var square = new Square()
square.width = 20
square.height = 5

println("(" + square.width + ", " + square.height + ")")    //  "(20, 5)"を出力

もうひとつ注目したいのが、引数を取らないメソッドも、プロパティへのアクセスと同じように呼び出すことができるという点です。例えばgetArea()メソッドは次のように()を省略して呼び出すことができます。

println(square.getArea)

また、メソッド名がgetXXXX()という形式の場合、次のように"get"を省略した形で呼び出すことも可能です。

println(square.area)

演算子オーバーロード

XtendではC++のように演算子をオーバーロードして独自の演算を定義できます。演算子オーバーロードを行うには、メソッド名の先頭に"operator_"をつけた特別なメソッドを定義します。たとえば"operator_plus"というメソッドを定義することで、+演算子の動作を定義することができます。-演算子の場合は"oparator_minus"です。演算子とメソッド名の対応はこのページにまとめられています。

次に示すVector2Dクラスでは、+、-、*の演算子をオーバーロードしています。*演算子に対応するメソッド名は"operator_multiply"です。new()メソッドはコンストラクタを表します。また、メソッドをオーバーライドする場合にはoverride修飾子を付けて宣言します。

package jp.gihyo.toolbox.xtend

class Vector2D {
    double x
    double y

    // コンストラクタ
    new(double x, double y) {
        this.x = x
        this.y = y
    }
        
    def Vector2D operator_plus(Vector2D v) {
        return new Vector2D(this.x + v.x, this.y + v.y)
    }
        
    def Vector2D operator_minus(Vector2D v) {
        return new Vector2D(this.x - v.x, this.y - v.y)
    }
        
    def Vector2D operator_multiply(double a) {
        return new Vector2D(this.x * a, this.y * a)
    }
        
    override String toString() {
        return "(" + this.x + ", " + this.y + ")"
    }
}

このように演算子に対応したメソッドを定義しておくと、次に示すようにそれぞれの演算子を使った演算が行えるようになります。

val v1 = new Vector2D(10, 20)
val v2 = new Vector2D(5, 10)
println(v1 + v2)    // "(15.0, 30.0)"を出力
println(v1 - v2)    // "(5.0, 10.0)"を出力
println(v1 * 5)     // "(50.0, 100.0)"を出力

ここで紹介した以外にも、JavaにはないXtendにはさまざまな機能が用意されています。まだ開発途上の言語ではありますが、Javaアプリケーション開発の効率を上げるツールのひとつとして見れば将来性のある言語だと思います。ぜひ活用してみてください。

おすすめ記事

記事・ニュース一覧