No Programming, No Life

プログラミング関連の話題や雑記

JavaでPropertyDescriptorを使ってBeanのプロパティに簡単にアクセスする

はじめに

Java標準のSDKjava.beans パッケージがありますが、この中にJavaBeansの getter/setter を便利に扱える PropertyDescriptor というクラスがあるようです。

クラス階層

java.beansClass PropertyDescriptorjava.lang.Object
 └java.beans.FeatureDescriptor
  └java.beans.PropertyDescriptor

使い方

それでは早速使い方を見て行きましょう

1. よくあるBeanをとりあえず定義
class User {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

こんな感じですよね、とりあえず、nameという名前のString型フィールドだけ持っているUserというBeanを考えてみましょう。

2. まずはメソッド(プロパティ)を指定してnewする

さて、早速PropertyDescriptorを使っていって見ましょう。いくつかコンストラクタはありますが、とりあえず今回はこんな感じで使ってみましょう。

PropertyDescriptor nameProp = new PropertyDescriptor("name", User.class);

コンストラクタの第一引数に getter/setter として定義されているメソッドの get/set を抜いた部分(プロパティ名)を指定して、第二引数に取得したいBeanのClassクラスを指定します。
上記のコードは nameProp という変数名で name のプロパティを取得したイメージです。

また、第二引数はClassクラスなので、上記例では、Userのstaticフィールド class で取得しましたが、直接インスタンスから取得してしまっても構いません。

User userBean = new User();
PropertyDescriptor nameProp = new PropertyDescriptor("name", userBean.getClass());

こんな感じですね。

3. Methodを取得する

さて、では次に作成した PropertyDescriptor からMethodを取得してみましょう。

まずはゲッターを取得してみましょう。ゲッターを取得するには #getReadMethod というメソッドを利用します。そう、ゲッターは読み込むのためのメソッドですよね。戻り値は java.lang.reflect.Method になります。

Method nameGetter = nameProp.getReadMethod();

次はセッターを取得してみましょう。勘のイイ方はお気づきですよね?そうセッターは #getWriteMethod で取得できます。セッターは書き込むためのメソッドですので。戻り値はゲッターと同様 java.lang.reflect.Method になります。

Method nameSetter = nameProp.getWriteMethod();

つまり、name というプロパティ名を指定して作った PropertyDescriptor(nameProp) から、ゲッターのメソッド(nameGetter) と セッターのメソッド(nameSetter) の両方が取得できたことになります。

4. ゲットやセットしてみる

さて、メソッドが取得できたので、ゲットやセットをしてみましょう。まずはゲットから。
呼び出しは Methodクラスの #invoke を利用します。#invokeは第一引数はそのメソッドをインヴォークしたいインスタンスを、第二引数はそのメソッドに渡したい引数をオブジェクト配列で渡すことになっています。

String name = (String) nameGetter.invoke(userBean, (Object[]) null);

注意として、ゲッターは引数がないので、nullを引数として渡すことになるんですが、nullだと引数の型が解決できなので、明示的に Object[] にキャストしてあげる必要があります。あと戻り値もObject型になってしまっているので、キャストしてあげましょう。結果はちゃんと文字列のインスタンスなんですが、見せかけ上の型がObjectになってしまうという話です。これは#invokeの第二引数は実際は具体的な型なんだけどもObject配列で渡しているのと同じ理由ですね。リフレクションではどんな型でも扱えるよう、インスタンスはObject型で操作するという。
ちょっと手がかかりますが、Javaのお茶目な一面ですね。

さて、ちと脇道に逸れましたが次はセッターに行きましょう。基本的にはゲッターと同じです。

nameSetter.invoke(userBean, "fumo");

第二引数は可変長引数 Object... になっていますので、わざわざ

nameSetter.invoke(userBean, new Object[]{ "fumo" });

とかって書かなくても平気ですよ! 便利な世の中ですね。

7. copyPropertyしてみる

では最後にプロパティのコピーをしてみましょう。ゲッターとセッターを組み合わせるだけで簡単に実現できますね、こんな感じです。

User user1 = new User();
User user2 = new User();

nameSetter.invoke(user2, nameGetter.invoke(user1, (Object[]) null));

nameSetter の #invoke の引数に nameGetter の #invoke した結果を直接入れるといった操作をここでは行っています。

8. ちなみに普通にリフレクションで書いたらどうなるか

普通にリフレクション版

try {
  Method nameGetter = User.class.getMethod("getName", (Class<?>[]) null);
  Method nameSetter = User.class.getMethod("setName", String.class);

} catch (SecurityException e) {
  e.printStackTrace();
} catch (NoSuchMethodException e) {
  e.printStackTrace();
} catch (IllegalArgumentException e) {
  e.printStackTrace();
} catch (IllegalAccessException e) {
  e.printStackTrace();
} catch (InvocationTargetException e) {
  e.printStackTrace();
}

↓↓↓
PropertyDescriptor版

PropertyDescriptor nameProp = new PropertyDescriptor("name", User.class);
Method nameGetter = nameProp.getReadMethod();
Method nameSetter = nameProp.getWriteMethod();

そうか、get/setのメソッド名の変換とかだけじゃなくて、例外処理とかもやってくれてるんだね。

おわりに

このクラスを知っていると、リフレクションのコードを書く際にちょっとだけ楽できますね。Java標準のSDKでもまだまだ知らないことがいっぱいあるなぁ。

テストで書いていたソースも載せておきます。

JavaEEでリクエストのHTTPヘッダに値を付加する

タイトルの通りなんですが、いまやってるプロジェクトでJavaEEでリクエストのHTTPヘッダに値を付加して持ち回る必要が出てきていまして、その方法を模索中です。

とりあえず色々ググってたどり着いた案は、今のところ以下のような感じ。

  • サーブレットだと、#setHeader() 出来るのはレスポンスの方だけで、リクエストの方は #getHerader() しかできない模様
  • HtteServletRequestWrapperを使って、#getHeader() された時の振る舞いをオーバーライドする

こんな感じで、ラッピングしたHttpRequestの方で新しくマップでHTTPヘッダを保持しておくってのはどうだろう。

ところでそもそもブラウザだとリクエストの時にHTTPヘッダっていじれないんですよね?まだあまり調べ切れてないけれど、簡単には出来なそうな感じでした。

ブラウザ経由なら普通にリクエストヘッダじゃなくてリクエストボディにデータを載せてポストしなきゃいかんのかなぁ。

HTTPクライアントを自分で書いちゃうような場合なら全然楽なんですけどね。

この方法で良いのかよく分からないけど、もっと良い方法があるんじゃない?っていう情報は大歓迎です!

追記

ブクマコメントで幾つかコメントをいただいていたようなので少し追記です。

  • なるべくセッションは使いたくないとのプロジェクトの方針なので難しいのですが、クッキーは今の所禁止はされていないようなので、HTTPヘッダにセットした値を持ち回るためにクッキーを利用するかもしれません。
  • HTTPヘッダに値をセット(TestRequestに追加したメソッド #setHeader()にて) するのは、filter のつもりでいます。
  • 想定はイントラだけではないので、セキュリティも慎重に考えないと危ないです。

うーん、想定しなきゃならんことが山積みですな。

Mac OSXにOpenJDK7をインストールしてGroovyを実行する

こちら@yusukeyさんの記事を参考にMac OSXにOpenJDK7をインストールしてGroovyを実行するところまで試してみたのでメモしておきます。

1.まずはOpenJDK7をダウンロード

Mac OSX用のバイナリが以下から手に入るのでダウンロードしてきます。
http://code.google.com/p/openjdk-osx-build/downloads/list
ひとまずこれを書いてる時点の最新バージョンである

OpenJDK-OSX-1.7-universal-20110729.dmg

をダウンロードしてきました。

2.インストール

  • OpenJDK-OSX-1.7-universal-20110729.dmg をダブルクリック
  • デュークをクリック

f:id:fumokmm:20110730160238p:image

  • 続ける(どんどん進む)

f:id:fumokmm:20110730160237p:image

  • インストール完了

f:id:fumokmm:20110730160236p:image

3.インストール確認

open /Applications/Utilities/Java\ Preferences.app

f:id:fumokmm:20110730161010p:image

Open JDK7が入りました。

4.環境変数JAVA_HOMEの設定

Open JDK7は以下にインストールされるようなので、以下を環境変数JAVA_HOMEに設定します。

/Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home

私の環境だとJAVA_HOMEは以下となっていました。

$ echo $JAVA_HOME
/Library/Java/Home

確認してみるとHOMEはシンボリックリンクだったので、ひとまず、今のHOMEはHome6として残しておき、Java7用としてはHome7という名前でシンボリックリンクを作成することにしました。
$ ln -s /System/Library/Frameworks/JavaVM.framework/Home /Library/Java/Home6
$ ln -s /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home /Library/Java/HOME7
$ rm /Library/Java/Home

次に.profileを以下のように編集します。

#export JAVA_HOME=/Library/Java/Home6
export JAVA_HOME=/Library/Java/Home7

いつでもHome6に戻せるようにHome6はコメントアウトしておきます。

$ . ~/.profile
$ java -version
openjdk version "1.7.0-internal"
OpenJDK Runtime Environment (build 1.7.0-internal-b00)
OpenJDK 64-Bit Server VM (build 21.0-b17, mixed mode)

1.7にちゃんとなってます。

5.Groovyの実行

バージョン確認

$ groovy -v
Groovy Version: 1.9.0-beta-1 JVM: 1.7.0-internal

スクリプト実行

$ cat hello.groovy 
println 'Hello, Groovy! with JDK7'
$ groovy hello.groovy
Hello, Groovy! with JDK7

スクリプト、実行できました。

GroovyShell実行

$ groovysh
Groovy Shell (1.9.0-beta-1, JVM: 1.7.0-internal)
Type 'help' or '\h' for help.

                                                                                                                                                                                                                                            • -

groovy:000> assert 1_0_0 + 2_0_0 == 3____00
===> null
groovy:000> assert 255 == 0b11111111
===> null
groovy:000> println 'Hello!'
Hello!
===> null
groovy:000>


GroovyShellも動きました。

GroovyConsole実行

$ groovyConsole &
/Applications/Utilities 557 $ java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:108)
at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:130)
Caused by: java.lang.NoClassDefFoundError: com/apple/eawt/ApplicationListener
at com.apple.mrj.MRJApplicationUtils.registerAboutHandler(MRJApplicationUtils.java:64)
at com.apple.mrj.MRJApplicationUtils$registerAboutHandler.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at groovy.ui.script13120101426311558877614.run(script13120101426311558877614.groovy:22)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1117)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1103)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1121)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1052)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:882)
at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:785)
at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:768)
at groovy.util.FactoryInterceptorMetaClass.invokeMethod(FactoryBuilderSupport.java:1274)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145)
at groovy.ui.view.MacOSXMenuBar.run(MacOSXMenuBar.groovy:46)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1117)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1103)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:361)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:882)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
at groovy.ui.Console$__clinit__closure23.doCall(Console.groovy:260)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:882)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:39)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at groovy.ui.ConsoleView$_run_closure1.doCall(ConsoleView.groovy:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:882)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
at groovy.ui.ConsoleView$_run_closure1.doCall(ConsoleView.groovy)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:882)
at groovy.lang.Closure.call(Closure.java:410)
at groovy.lang.Closure.call(Closure.java:404)
at groovy.util.FactoryBuilderSupport.dispathNodeCall(FactoryBuilderSupport.java:838)
at groovy.util.FactoryBuilderSupport.doInvokeMethod(FactoryBuilderSupport.java:744)
at groovy.util.FactoryBuilderSupport.invokeMethod(FactoryBuilderSupport.java:464)
at groovy.util.FactoryInterceptorMetaClass.invokeMethod(FactoryBuilderSupport.java:1272)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145)
at groovy.ui.ConsoleView.run(ConsoleView.groovy:51)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1117)
at groovy.util.FactoryBuilderSupport.build(FactoryBuilderSupport.java:1103)
at groovy.util.FactoryBuilderSupport$build.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at groovy.ui.Console.run(Console.groovy:301)
at groovy.ui.Console$run$0.callCurrent(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
at groovy.ui.Console.run(Console.groovy:264)
at groovy.ui.Console$run.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
at groovy.ui.Console.main(Console.groovy:199)
... 6 more
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.ApplicationListener
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 108 more

現場で使えるGroovy(その3)「JavaにREPLが無いならgroovysh, groovyConsoleを使えばいいじゃない」

このシリーズの一覧はこちら

はじめに

ご存知の通り、JavaにはREPLがありません。しかし残念がることはありません、そう、あなたにはGroovyが付いています。

Groovyがこれらの特性とJavaとの親和性を高いレベルで同時に実現していることです。
          (中略)
Javaに極めて近い文法(ほぼ上位互換)
JavaコードはおおむねそのままGroovyコードとしても動作
          (中略)
JDKの標準ライブラリやサードパーティのライブラリ/フレームワークもすべて共用可能
          (中略)

『プログラミングGroovy』P8 より

ということで、GroovyのREPLであるgroovyshや、groovyshよりも便利なgroovyConsoleを利用すればJava自体のコード、クラスを素早く動かしてチェックすることができるわけです。
さっそくやってみましょう。

ケーススタディ1:Integerクラスのメソッドの挙動を知りたくなった

さて、それではたとえばIntegerクラスの static String toBinaryString(int i) メソッドの使い方が気になってしまったとしましょう、そんな時は迷わすgroovyshを起動しましょう。

$ groovysh
Groovy Shell (1.8.0, JVM: 1.6.0_26)
Type 'help' or '\h' for help.

                                                                                                                                                                                                                                            • -

groovy:000>


さて、準備は整いましたね。それではおもむろに使ってみましょう。
groovy:000>  Integer.toBinaryString(100)
===> 1100100
groovy:000>

おぉ、1100100 という答えが返ってきましたね。これはもちろんJavaで試した場合も同じ挙動をするはずです。だってIntegerはJava標準ライブラリですからね。

ではお次はgroovyConsoleを使ってみましょう。groovyConsoleの起動方法はいくつかありますが、ここではコマンドラインから起動させてみます。

$ groovyConsole 

f:id:fumokmm:20110708222423p:image

起動しましたか?ではさっきと同じようにInteger.toBinaryString(100)をやってみましょう。
f:id:fumokmm:20110708222424p:image

入力が完了したら、Ctrl + R *1で実行です。
groovyConsoleを使えば少しコードを書き換えて何度も実行してみる時にとても重宝します。
さらに、Groovyの機能もすべてが使えるため、たとえば、1から100で値がどうなるかなどを簡単に調べてみたりすることができます。
f:id:fumokmm:20110708222425p:image

また、スクリプトとしてコードを保存できたり、スクリプトを読み込んだりして使うこともできるのです。
f:id:fumokmm:20110708222426p:image

すごく便利ですよね?

おわりに

ということで、使い方はあなた次第です。ぜひ実際の現場でもgroovysh, groovyConsoleを常に立ち上げてGroovyのみならずJavaとも戯れてみて下さい!

Enjoy your code more groovier!

参考書籍

Groovyに興味を持った方はぜひこの一冊を購入しましょう!

プログラミングGROOVY

プログラミングGROOVY

*1:Macの場合はCMD + R。

5分で理解するProject Coin

はじめに

そろそろJDK7が登場する時が近づいてきたのでJavaプログラマならProject Coinを先取りして予習しておくべき時期になってました。Project CoinというのはThe Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 334となっているもので、Javaのコードをちょっとだけ簡単にしてくれるような文法の変化をもたらしてくれます。全部で5つありますので、一つ一つ見て行きましょう。

1. switch文でStringを (String in switch)

今はまだswitch文は整数値(char, byte, short, int)とそのラッパークラスしか使えないのはご存知のとおり。例えば以下のような文字列比較のif-else文を考えてみて下さい。

if ("A".equals(str)) {
} else if ("B".equals(str)) {
} else if ("C".equals(str)) {
} else {
}

ちとイケテナイですね。これがProject Coinになると以下のようにシンプルになります。

// switchでString!
switch (str) {
  case "A":
    break;
  case "B":
    break;
  case "C":
    break;
  default:
    break;
}

あらすっきり。地味に便利。

2. マルチキャッチ (Multi catch)

多くのメソッドが一つ以上のチェック例外を投げる可能性がある時、それらを全部捕捉してログに出してさらに投げ直すなんてことをしないといけません。

try {
  // Some EJB call
} catch (NamingException e) {
  log.error(e);
  throw new RuntimeException(e);
} catch (IOException e) {
  log.error(e);
  throw new RuntimeException(e);
}

ちとイケテナイですね。これがProject Coinになると以下のようにシンプルになります。

try {
  // Some EJB call
} catch (NamingException | IOException e) { // ←マルチキャッチ!
  log.error(e);
  throw new RuntimeException(e);
}

パイプORで繋ぐんですね、これeの型はその都度変わるってことでいいんでしょうか?

2011-04-24追記

マルチキャッチ構文で書いた場合はコンパイルするとマルチキャッチの部分を展開して、同じ例外処理を行なうコードに書き換えているようです。つまり最初のJavaコードのように書いたのと同じ効果が得られるというわけです。
また、eはマルチキャッチする例外クラス全てで定義されているメソッドのみ使用可能となるようです。なのでどれかだけにあるようなメソッドを使おうとするとコンパイルエラーになるようです(参照)。

3. リソース付きトライ (Try with resources)

プログラミングでよく犯しがちなミスとしてメモリリークがあります。これは例外発生時にクローズ処理などをし忘れてしまうことによる原因が多いです。なので以下の例のようにfinallyブロックできちんとクローズ処理を入れることを忘れないようにしないといけません。

Connection conn = null;
try {
    conn = dataSource.getConnection();
    // run query
} catch (SQLException e) {
} finally {
    if (conn != null) {
        try {
            conn.close();
        } catch (Exception ignore) {
        }
    }
}

Project CoinのJDK7では java.lang.AutoCloseable という新しいインタフェースが登場します。このインタフェースはclose() というメソッドだけを持っていて、InputStream, Reader, java.sql.Connection のようなクラスがこのインタフェースを実装しています。AutoCloseable なクラスはtry-with-resourceステートメントで使うことができます。コードがtryブロックを実行し終えると、close() メソッドが暗黙的にリソースを解放するために呼び出されます。上記で示したコードをJDK7スタイルにすると以下のような感じになります。

// try-with-resourceステートメント!
try (Connection conn = dataSource.getConnection()) {
    // run query
} catch (SQLException e) {
}

for文の初期値のような感じでConnectionを宣言して使っているイメージなんですかね。自動でリソースを解放してくれるのは楽でいいですね。これはよいインタフェース。

4. 型推論 (Type inference)

総称型のインスタンス生成時の話です。Javaだとこんな感じになりますよね。

Map<String, List<String>> map = new HashMap<String, List<String>>();

うーん、冗長だなぁ。そこで、Project Coinでは右側だけすっきりさせられるようになりました*1

Map<String, List<String>> map = new HashMap<>(); // ←右だけすっきり!

<>という形から通称「ダイヤモンド」と呼ばれているようです。
ね?すっきりしたでしょ?え?Scalaとかに比べると微妙だって?それは言わないお約束:-)

ちなみにScalaだと
val map = Map[String, List[String]]()

5. 2進数リテラルと数値のアンダースコア区切り (Binary integral literals and underscores in numeric literals)

Project CoinのJDK7からは2進数用のリテラルが使えるようになりました。ちょうど16進数を表すのに頭に "0x" を付ける感じで、2進数の場合は頭に "0b" を付けます。さらに、数値リテラル中に区切り文字としてアンダースコアを入れられるようになりました。

1234_5678
1_2_3_4__5_6_7_8L // アンダースコアはいくつあってもOKのようです
0b0001_0010_0100_1000
3.141_592_653_589_793d
0x1.ffff_ffff_ffff_fP1_023 // Double.MAX_VALUE

また、以下のような感じに、アンダースコアの位置が悪いと使えないようです。

_1234
0x_1234
1234_
0x1.0_p_-1022

ちなみにRubyの数値リテラルでも出来てますね。やっとJavaでもできるようになったということで。

修正履歴

*1:右なんだ…宣言する型だけはしっかり書かせるわけですね。

Java使いをGroovyに引き込むサンプル集

はじめに

Java使いをScalaに引き込むサンプル集 | mwSoft のGroovy版を書いてみました。
記事中に登場するサンプルコードや文章など多くの部分を引用させていただいております。
(動作確認: Groovy Version: 1.7.7 JVM: 1.6.0_22)

前書き

Groovyという言語をご存知ですか?
Javaと同じくコンパイルされるとclassファイルになり、実行時はJVM上で動作し、またスクリプトとしても記述可能なオブジェクト指向スクリプト言語です。
Groovyは後発の言語ということもあって、Javaを書いている時に感じる冗長さに対する様々な解が用意されています。
本記事では、GroovyとJavaのコードを比較しながら、JavaユーザがGroovyに移った際に得られるメリットを提示していきます。
尚、序盤のサンプルコードはJavaユーザに伝わりやすいように、returnを明記したり、メソッドは必ず{}で囲むなど、極力Javaっぽい記述をしています。

妥当なJavaコードはほぼそのまま妥当なGroovyコードです

極端な例を言えば、Hoge.javaの拡張子をHoge.groovyにするだけでGroovyコードのできあがりです。
ステップを追ってJavaをGroovyに変換する例は をご参照下さい。

初期化するだけで折り返しが必要になったこと、ありませんか

Javaを使っていると、大した処理でもないのにやたらと長い記述が必要になることがあります。たとえば初期化Javaには長い名前のクラス名がたくさんあります。
以下はDOMでXMLを解析する際の前処理です。

【Java】
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("foo.xml"));

ただ初期化するだけだというのに、ひどく長い文字数が費やされています。Genericsの記述などが絡めば、初期化だけで平気で折り返しが必要になることもあります。
Groovyではまず、動的型付けにより、クラス名を明記しなくても動的に動作します。
なので、記述的にはこんな風になります。

【Groovy】
def factory = DocumentBuilderFactory.newInstance()
def builder = factory.newDocumentBuilder()
def doc = builder.parse(new File("foo.xml"))

動的型付けのお陰で、記述がちょっとスッキリしました。

さらにGroovyでは、importしたクラスに別名を付ける機能も用意されています。

別名importは下記のような記述になります。

import javax.xml.parsers.DocumentBuilderFactory as DBFactory

これを利用した上で、最初のJavaのコードを記述すると、こんな風になります。

【Groovy】
def factory = DBFactory.newInstance()
def builder = factory.newDocumentBuilder()
def doc = builder.parse(new File("foo.xml"))

最初のJavaのコードに比べると自明な冗長な部分が減っています。
また、動的型付けによって余分なimportも減ります。Javaの例では3つのクラスをimportする必要がありますが、Groovyの例ではDocumentBuilderFactoryのみimportすれば、残りの2つは実行時に勝手に解決してくれます。

Listや配列の初期化が面倒だと思ったこと、ありませんか

JavaではListや配列の初期化がけっこう面倒です。普通に配列を初期化するだけなら、こんな風に書けます。

【Java】
String[] array = {"abc", "def", "ghi"};

これは楽なのですが、上記の式が使えるのは初期化の時のみ。下記のような記述はエラーになります。

【Java】
String[] array = null;
array = {"abc", "def", "ghi"};

なので初期化以外の場所、たとえば引数の中でさくっと配列を渡したい場合は、こんな記述になります。

【Java】
foo(new String[] { "abc", "def", "ghi" });

Listの初期化の場合は、Arrays.asListを使うのが速いでしょうか。

【Java】
Arrays.asList("abc", "def", "ghi")

Groovyではリテラルとしてリストを提供しています。["要素1", "要素2", ...] as String[] のように書けば配列が、["要素1", "要素2", ...] のように書けばList*1が生成できます。

【Groovy】
def array = ["abc", "def", "ghi"] as String[]
def list = ["abc", "def", "ghi"]

引数の中でも、再代入の際でも、どこでもこれ1つでListやArrayを生成できるので、Javaよりちょっと楽です。

デフォルト引数を欲しいと思ったこと、ありませんか

Javaでは引数にデフォルト値を入れたい場合、メソッドを複数個定義する必要が出てきます。

例えば、消費税を計算する際に、デフォルトを5%にして、任意で税率を引数に渡せる処理を書く場合、以下のように2つメソッドを定義します。

【Java】
public int shohizei(int kane) {
    return shohizei(kane, 1.05f);
}
public int shohizei(int kane, float zei) {
    return (int) (kane * zei);
}

Groovyではスクリプト言語などで良く用いられているデフォルト引数が用意されています。
デフォルト引数を使えば、以下のように1つのメソッドを定義するだけで上のコードと同じ振る舞いをさせることができます。

【Groovy】
def shohizei(yen, rate = 1.05) {
    (yen * rate).toInteger()
}

Groovyではメソッド呼び出し時に名前付きで渡す方法も用意されています。
この方法を使えば、以下のように3つの引数のうち、2つ目と3つ目に値を指定するという記述もできるようになります。

def foo(Map param) {
  println(
    (param?.c1 ?: 'A') +
    (param?.c2 ?: 'B') +
    (param?.c3 ?: 'C')
  )
}
// そのまま呼び出すとデフォルト引数が適用される
foo()
  // => ABC

// 名前付きで指定すれば、2つ目と3つ目に適用するような指定もできる
foo(c2: 'E', c3: 'F')
  // => AEF

便利。

補足
  • ?.演算子はレシーバがnullだった場合、NullPointerExceptionを発生させず、単にnullを返却します。
  • ?:演算子*2は a ? a : b だった場合に a ?: b と書けるようにした略記法です。
  • ここはScalaの名前付き引数のほうが便利な気がする。

throwsを書くのを面倒だと思ったこと、ありませんか

Javaでは発生しうる例外をメソッドの宣言時に記述するthrowsというキーワードがあります。

【Java】
public Connection getConnection() throws SQLException {
    return DriverManager.getConnection("jdbc:sqlite:hoge.sqlite3");
}

throwsを指定されたメソッドは、利用する際に指定された例外をcatchかthrowsする記述を入れないとコンパイルエラーになるので、例外処理を忘れずに記述できるという点で効果があります。

ただ、多くの開発現場でそれらは適切に使われずに、RuntimeExceptionを継承したオリジナルの例外でラップしたり、面倒だからExceptionでひとまとめにcatchするなど、適切に使われていないケースも良く見かけます。

【Java】
// 面倒だからExceptionでまとめてthrows
public void hoge() throws Exception {
    // 処理
}
// RuntimeExceptionでラップしてthrow
public void fuga() {
    try {
        // 処理
    } catch (Exception e) { 
        throw new RuntimeException(e);
    }
}

便利な機能ではあるけど、人間が管理しようとするとthrowsはけっこうヘビーです。
というわけで、Groovyではthrowsを亡き者にしました。

【Groovy】
// throwsを指定しなくても呼び出せる
def getConnection() {
    DriverManager.getConnection("jdbc:sqlite:hoge.sqlite3")
}

Javaのクラスと連携するために、アノテーションでthrowsを指定することはできますが、Groovyだけを使ってる場合は関わることはなくなります。

文字列の比較は罠だと思ったこと、ありませんか

Javaの文字列の比較は、equalsメソッドを利用します。

== を使用した場合は同一の参照先を指しているかを比較することになるので、その文字列を生成した際の状況によって結果が変わったりします。

【Java】
String str1 = "テスト";
String str2 = "テスト";
String str3 = new String("テスト");

System.out.println(str1 + " == " + str2 + " = " + (str1 == str2));
  //=> テスト == テスト = true
System.out.println(str1 + " == " + str3 + " = " + (str1 == str3));
  //=> テスト == テスト = false

慣れてしまえばそれまでなんですが、ここは多くの人が一度は引っかかるポイントです。

Groovyはintやdoubleといった数値についてもプリミティブ型ではなくオブジェクト*3なので参照先を比較する == の出番というのはほとんどなくなってしまいます。
というわけで、Groovyでは == は値を比較するようになりました。

【Groovy】
def str1 = "テスト"
def str2 = "テスト"
def str3 = new String("テスト")

println "${str1} == ${str2} = ${str1 == str2}"
  //=> テスト == テスト = true
println "${str1} == ${str3} = ${str1 == str3}"
  //=> テスト ==  テスト = false

new String()した値も、== すればtrueと判定されます。
参照先の比較を行う場合は、isを用います。

【Groovy】
println "${str1} is ${str2} = ${str1.is(str2)}"
  //=> テスト is テスト = true
println "${str1} is ${str3} = ${str1.is(str3)}"
  //=> テスト is テスト = false
補足
  • Groovyでは文字列に ${...} という形式で変数や式を埋め込むことができます。

分かりきってる記述は省略したいと思ったこと、ありませんか

Groovyは省略できることは省略することを良しとしている言語です。

例えばJavaで2つの数を足して返すメソッドを記述すると、以下のようになります。

【Java】
public int sum(int i1, int i2) {
    return i1 + i2;
}

Groovyで上記のコードを極力似たように記述すると、こうなります。

【Groovy】
public int sum(int i1, int i2) {
    return i1 + i2;
}

そうですね、Javaと全く同じ記述ですが、これでも立派なGroovyコードです。
さて、省略できるところを1つずつ削っていってみましょう。
まず、Groovyではデフォルトの可視性はpublicなので省略可能です。

【Groovy : public抜き】
int sum(int i1, int i2) {
    return i1 + i2;
}

行末がセンテンスの切れ目になることは自明のことです。わざわざ自明のことのためにセミコロンを何度もタイプするのは無駄です。Groovyでは1行に2つの文を詰め込みたい時を除いてセミコロンは必要ありません。

【Groovy : セミコロン抜き】
int sum(int i1, int i2) {
    return i1 + i2
}

Groovyでは最後に評価した値が自動的に戻り値になります。なのでreturnはいりません。

【Groovy : return抜き】
int sum(int i1, int i2) {
    i1 + i2
}

型の指定はなくてもダックタイプが働きます。

【Groovy : 型指定抜き】
def sum(i1, i2) {
    i1 + i2
}

ついでにワンライナーにしましょう。

【Groovy : 型指定抜き】
def sum(i1, i2) { i1 + i2 }

ふぅ、スッキリしました。

このようにGroovyはいろんな記述を省略できます。最低限の可読性を保ちつつJavaコードから推測しやすいレベルですので、慣れてしまえばタイプ数が減らせるありがたい機能だと思えるようになります。

補足
  • ちなみに残念ながら引数がないメソッドの場合でも括弧は省略はできません。def hello() {} が最大限に省略した形です。

try catchの記述を共通化したくなったこと、ありませんか

Javaでコードを書いていると、いろんな箇所にtry catchの記述が現れます。

【Java】
try {
    // 処理
} catch (SQLException e) {
    e.printStackTrace();
}

このよくある処理を部品化しようとすると、インタフェースを作ってそれを実装したクラスを共通のtryが入ったクラスの中で呼び出すような、ちょっとした構造を考える必要があります。

Groovyには引数に処理ブロックを渡すことができるクロージャ(Closure)があります。これを使えばtry catchとか、最後に必ずcloseするとか、そういった定形の処理を簡単に記述できます。

たとえばこんな感じです。

【Groovy】
// メソッドを引数に取って、try catchの中で実行させるメソッド
def tryCatch(Closure proc) {
    try {
        return [proc.call()] // 結果をリストに詰めて返却
    } catch (Exception e) {
        return [] // 空リストを返却
    }
}

// 上のtryCatchメソッドを使って割り算するメソッド
def divide(i, j) {
  tryCatch {
    (i / j) as int
  }
}

// 実行してみる
divide(10, 3)
  // => [3]

divide(10, 0)
  // => []

まずtryCatchという、例外が起きたら空リスト()を、それ以外だったら処理した結果を返すメソッドを用意します。結果をリストでラップすることで失敗時を表現しています。((ScalaだったらOption[T]があるので便利なのですが、Groovyには残念ながらない。))
次にdivideというメソッドで、tryCatchメソッドを呼び出しつつ、中で i / j を実行するように記述しています。
divideを呼び出すと、iをjで割った値をintにキャストして返します。この時、0で除算すると当然例外が起きるわけですが、その場合はtryCatchの中でcatchされて空リスト(
)が返ります。このtry catchはどんな処理に対してでも適用できます。
クロージャ(Closure)を使うと、Javaでは部品化するのに記述量がけっこうかかりそうなことが、こんな風に割とあっさり記述できます。

補足
  • ちなみに上記の例でtryCatchメソッドでreturnが省略できないのは、try-catchが値を返さないからです。

Listを回して中身を処理することって、多いよね

プログラムを書いていると、リストの中身を回してちょっと加工して、新しいリストを作る、という要件に頻繁に出くわします。
Groovyには、ループを記述してその中に処理を書きこまなくても簡単にデータの加工ができる、便利な機能が大量に用意されています。たとえば以下のような。

【Groovy】
// こんなListがあったとさ
def list = [1, 2, 3, 5, 3, 5, 7, 8]

// ユニークな要素を抽出する (注:#unique()は破壊的!)
list.unique()
  // => [1, 2, 3, 5, 7, 8]

// 偶数と奇数で分けてみる
list.groupBy{ it % 2 }
  // => [1:[1, 3, 5, 3, 5, 7], 0:[2, 8]]

// 合算してみる
list.sum()
  // => 34

// 左から順に乗算していく
list.inject(1){ x, y -> x * y }
  // => 25200

// すべての値を2倍したListを作る
list.collect{ it * 2 }
  // => [2, 4, 6, 10, 6, 10, 14, 16]

// 2つの要素の差分を取る
[1, 2, 3, 4, 5] - [1, 4, 5]
  // => [2, 3]

しかもこれらの機能は、GroovyがJavaのListを拡張しているため、何もしなくてもそのまま利用することができます。

補足
  • コメントでも書いてありますが、#unique()はリストに対して破壊的操作を施します。よって、新しいリストのインスタンスが返ってくるわけではなく、リストの内容が直接書き換えられてしまいます。

後書き

というわけで、Java使いの皆さんに馴染み深そうな部分でGroovyのメリットを並べてみました。
Groovyはまだまだ日本では普及しておらず、そもそもそんな言語があることすら知らないという人がほとんどだと思います。Groovyは動的言語ということでIDEサポートが弱かったり、動作がJavaより遅いと言われていたりします*4。Groovyを仕事で使う機会なんてほとんど出会えなかったり等、新興の言語にありがちな問題をいくらか抱えてます。それらの問題が十分に解決されれば、今後、この言語が主流になっていくこともありえるんじゃないかと個人的には予想しているのですが、どうでしょう。
Groovyは元記事Scalaと同じJavaVM上で動く言語ということで、いわば兄弟のような存在です。ここの記事で紹介された内容は今日、今すぐにでも始められるような内容が多いと思います。Scalaが取っているアプローチと比較しながら楽しんでいただけたら幸いです。

おわりに

素敵な記事を書いていただいた元記事作者(mwSoft様)に感謝致します。

*1:正確にはjava.util.ArrayList です。["要素1", "要素2", ...] as LinkedList などのように as でキャストできます。

*2:エルビス演算子と呼ばれています。

*3:intはjava.lang.Integerの別名, doubleはjava.lang.Doubleの別名

*4:ただこれらについては[http://code.google.com/p/groovypptest/:title=Groovy++]という面白い試みも現在進行中で成されており、今後の動向に期待を寄せているところです

JavaからGroovyへ移行する簡単なステップ

この記事は From Java to Groovy in a few easy steps | Groovy Zone の和訳です。思いっきり意訳です、あしからず。

GroovyとJavaは、いとこみたいに近い存在で、構文がとても似ているからJava開発者にとってGroovyを覚えることはとても簡単です。Javaプログラムのほとんどはそのまま、有効なGroovyプログラムなんです!でも、Groovyを学ぶようになるとそのうちにGroovyに用意されている気の利くデフォルト値やGStringなどショートカット記法を使うようになっていくでしょう。

この記事では最初にJavaプログラムを取り上げ、それをGroovyに置き換えてゆくプロセスをご紹介します。まずは少々馬鹿げているかもしれませんが、複雑なHello Worldプログラムから始めることにしましょう。後の方の記事ではより高度な例をご紹介します。これは、会議の席で見たGroovyとJavaの構文がとても近いことを紹介するスライドの何枚かからインスピレーションを得ました。アンドレスさんとポールさんのアイデアに感謝します。

まずはこんな感じのJavaプログラムを用意しましょう。

public class HelloWorld {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String greet() {
        return "Hello " + name;
    }
    
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.setName("Groovy");
        System.out.println( helloWorld.greet() );
    }
}

さて、このハローワールドクラスは、ゲッター/セッターとそれに紐付くprivateフィールド、それからgreet()メソッドがありますね。greet()メソッドは悪名高いハローワールド文字列を返します。それに親しみや憎しみを感じたものですね*1。そして、main()メソッドがクラスをインスタンス化して、挨拶を出力します。

さてGroovy版を見てみましょう。

public class HelloWorld {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String greet() {
        return "Hello " + name;
    }
    
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.setName("Groovy");
        System.out.println( helloWorld.greet() );
    }
}

そうです、まったく同じプログラムです!馬鹿にしてるわけじゃなくて、Java版とGroovy版はまったく同じだってことです。でもこのプログラムはまだGroovyプログラムとはおせじにも呼べない代物です。もっと読みやすくて簡潔に記述できますよ。それじゃあ、単純なGroovy化*2ステップを適用していってみましょう。まず最初のステップはセミコロンの削除とpublicキーワードの削除です。Groovyではクラスやメソッドはデフォルトでpubilcなので明示的に指定する必要がないんです。ということで、こんな感じになります。

class HelloWorld {
    private String name

    void setName(String name) {
        this.name = name
    }

    String getName() {
        return name
    }

    String greet() {
        return "Hello " + name
    }
    
    static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld()
        helloWorld.setName("Groovy")
        System.out.println( helloWorld.greet() )
    }
}

あとは何ができるでしょうか。ここでGroovyの便利な文字列、GStringを使ってみましょう。なにその機能、おいしいの?GStringって何なの?他の言語では "補間文字列" とかって呼ばれているやつです。Javaで言うところの通常の文字列のようにダブルクォーテーションで囲んだ文字列では、プレースホルダーが使えます。${someVariable} みたいな感じで。文字列が出力される際に置換されます。なので、手動で文字列結合とかする必要がないんです。じゃあ、さっきのgreet()メソッドはどうなるかって?

String greet() {
    return "Hello ${name}"
}

さらにもう一つ。returnキーワードは省略できます、省略した場合最後に評価された値が返却値となります。これで、greet()メソッドはさらにほんのちょっと短くなりますね。ここまでの内容をまとめてみましょう。プログラムは以下のような感じになりますね。

class HelloWorld {
    private String name

    void setName(String name) {
        this.name = name
    }

    String getName() { name }

    String greet() { "Hello ${name}" }
    
    static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld()
        helloWorld.setName("Groovy")
        System.out.println( helloWorld.greet() )
    }
}

getName()メソドとgreet()メソッドはワンライナーですっきりさせました。さて、次はどうしましょうか。Groovyはプロパティをサポートしています。*3だから、ゲッター/セッターとそれに紐付くprivateフィールドはプロパティ化できます。Groovyでは特に可視性を指定せずに単にフィールド宣言するだけでOKです。nameプロパティは単純にStringです。privateフィールドと、それに紐付くゲッター/セッターはGroovyが自動で用意してくれます。setName()呼び出しは、helloWorld.name = "Groovy" のように使えるようになります。getName()は、単にhelloWorld.name となります。これは既存のJavaクラスについても同様で、たとえば、HogeクラスがgetName()とかsetName()を持っているなら、hoge.name で呼び出せます。*4さて、どうなったかな?

class HelloWorld {
    String name

    String greet() { "Hello ${name}" }
    
    static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld()
        helloWorld.name = "Groovy"
        System.out.println( helloWorld.greet() )
    }
}

Groovyにはよく使うメソッドのお手軽なショートカットが用意されています。System.out.println() は println() だけで使えます。GroovyはJDKで提供されているクラスにもユーティリティメソッドを追加していたりします。その名もGDKです。トップレベルステートメントではメソッド呼び出しの括弧を省略することができます。

class HelloWorld {
    String name

    String greet() { "Hello ${name}" }
    
    static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld()
        helloWorld.name = "Groovy"
        println helloWorld.greet()
    }
}

そういえばここまで、全てのメソッドや変数宣言の時に、型指定していましたね。Groovyは動的型付けもサポートしていますので、もしお望みなら、全部型を取っ払ってしまうこともできます。

class HelloWorld {
    def name

    def greet() { "Hello ${name}" }
    
    static main(args) {
        def helloWorld = new HelloWorld()
        helloWorld.name = "Groovy"
        println helloWorld.greet()
    }
}

Stringをdefキーワードに変換しました。main()メソッドのvoidキーワードも要りません。引数のString配列も無くしてしまいましょう。

Groovyはオブジェクト指向言語(数値なども含めて全てがオブジェクト)であり、Javaと同じプログラミングモデルをサポートしています。さらにGroovyはスクリプト言語でもあるので、Javaのようにクラス定義を強制されない自由な書き方が可能です。このチュートリアルの最後のステップは、完全にメインメソッドを取り除くことです。

class HelloWorld {
    def name
    def greet() { "Hello ${name}" }
}    

def helloWorld = new HelloWorld()
helloWorld.name = "Groovy"
println helloWorld.greet()

スクリプトは本当に手軽にちょっとした文の集まりでプログラミングしていくことができます。あなたもHelloWorldクラスをGroovy化したみたいに、いろんなクラスをGroovy化することができるんです。

これで退屈なJavaプログラムはとてもグルービーなものとなります。ここで学んだ単純なGroovy化のステップを適用していくことで、Groovy言語で用意されている数多くの便利な機能の活用方法の一端に触れることができました。プログラムは、はるかに簡潔で読みやすく、Javaと同等のことが実現できました。読みやすさはコードメンテナンス時にとても重要になります。特に自分で書いたコードじゃない場合などは、実際にそのコードを書くよりもそのコードを読んで理解するのに多くの時間を費やす必要があります。

Enjoy!

*1:なぜ親しみや憎しみを感じたのか、なぜハローワールド文字列が悪名高いのか、私には理解できませんでした…あちらの方のフィーリングが何かあるんだろうなぁ。

*2:元記事では、Groovyfy:グルービファイと表現されていました。カッコイイ言い方なので流行らせたい!

*3:Java7だか8だかで正式サポートされる可能性があります。

*4:hogeはHogeクラスのインスタンスと仮定。