LombokとLombok-pg: Javaコードを減量する魔法のスパイス

この記事はJavaアドベントカレンダー2014の12月24日分です。昨日は、 nagaseyasuhito さんによる Mavenでマスター/スレーブ構成のMySQLを起動して結合テストをするぞ という記事でした。Mavenでこんなことまでできるんだなぁということが分かる実践的なコードで、参考にしたいです! 明日は、いよいよアドベントカレンダー最終日、担当は kokuzawa さんになります。

コードをシンプルにできるラムダ式への注目

さて今年の日本のJavaアドベントカレンダーは、4月にJava 8がリリースされたこともあって、Java 8に関連した記事が多かったようです。その中でも、特にラムダ式への注目が際立ちました。

ラムダ式の最たる活用例 Stream APIについては、12月17日のcom4dcさんがデータ処理がどう簡潔に書けるかをコード例で示されています。また12月20日のRyota Murohoshiさんは、JavaのStream APIを学ばれる過程で分かったStream API、引いてはJavaの標準ライブラリの設計思想を紹介されています。ぼくが昨年のアドベントカレンダーで書いたStream APIの始め方 も合わせてどうぞ。

ライブラリのラムダ式対応が進んでいることが分かる例として、12月10日のsuke_masaさんがO/RマッパDomaのラムダ式対応、12月16日のzephiransasさんがふるまいの仕様をラムダ式を使った英語風のDSLで既述できるlambda-behaveを紹介されています。

ラムダ式を活かせるライブラリを自作されてる方もチラホラいらっしゃいました。12月3日のtaichiさんが 軽量WebアプリフレームワークSiden、12月18日のequus52さんがパターンマッチのマッチ式風のライブラリpattern-matching4j を公開しています。

少し違った観点から、12月2日のmike_neckさんが オーバーロードしたメソッドでのラムダ式のワナについて書かれています。ぼくも、ライブラリ自作中にこのワナを踏んでしまったので、将来のJavaでの改善を期待したいところです。

こんな風に多くのJavaプログラマにラムダ式が歓迎されている最大の理由は、なんといっても「わずらわしかった匿名クラスがいらなくなり、コードをシンプルにできる」ところだよな、とぼくは思います(匿名クラス自体は良いアイデアだと思いますが、使うのは面倒でした)。

ラムダ式以外のコードシンプル化方法

本記事は、そんなラムダ式とはまた違ったJavaのコードシンプル化をご紹介します。

次の2つを取り上げます。

  • Javaのボイラープレートを自動生成してくれるライブラリLombok(ロンボク)
  • Lombokに機能を追加したLombok-pg

Javaの冗長なボイラープレート

ボイラープレートとは、「プログラミング言語やフレームワークなどの仕様上、どうしても省略できないお決まりのコード」のことです。

Java 8のラムダ式も、メソッドに渡したい関数(コードのかたまり)を表す匿名クラス、というボイラープレートを削減してくれるものですね。またJava 7で導入されたtry-with-resource文も、ファイル入出力などをするさいのリソース管理のボイラープレート(やリソース管理のうっかりミス)をかなり削減してくれました。Java 5のジェネリクスオートボクシングも、型キャストのボイラープレート(やキャスト失敗の危険)を削減してくれました。

このようにJavaは、バージョンを経るごとに、コードを安全にそしてシンプルに書けるように発展してきました。

ですがJavaには、まだまだ多くのボイラープレートがあります。代表的なものだと、JavaBean規約などで定められているsetter/getter/デフォルトコンストラクタや、equals/hashcodeの実装などが思い浮かびます。

IDEによるボイラープレート自動生成

そんなJavaのボイラープレートのいくつかは、EclipseやIntellij IDEAなどのIDEで、少ない手間で生成できます。IDEを使いこなしている人であれば、さほど不便を感じてないかもしれません。

ただ、IDEのコード生成でも、地味にわずらわしいことがあります。

例えば、フィールドが増えたりしたときに、setter/getter/equals/hashcodeを生成しなおす手作業は、ぼくは面倒に感じます。うっかりequals/hashcodeを修正し忘れてしまうと、HashMapやHashSetなどが意図に反した動きをしてしまいかねません。

また、重要でないgetter/setterなどでコード量がムダに増えてしまうと、本質的でないコードでテストカバレッジが下がってしまいますし、コードを読み書きする上であまり気持ちがよいものではありません。

Lombok:Javaのコード量をダイエットするスパイス

そこでご紹介するのが Lombok です。

Lombokは、Javaのアノテーションプロセッサを利用し、コンパイル時にコードを自動生成しすることで、ボイラープレートを削減してくれるものです。これにより「自動生成しなおす煩わしさ」や「コード量のうっとうしさ」から、だいぶ解放されます。

ちなみに、Lombokという名前は、インドネシアのロンボク島から取ったようです。ロンボク島は、独特のスパイス料理があるらしく、Java(ジャワ島もインドネシアの島です)にピリッとひと味加える、というような意図なんでしょうね。

そんなLombokについては、既に色々な記事で書かれており、使っている方も多いかもしれません。まだ知らなかった方へは、この辺の記事をオススメします。

コード例や使い方の紹介は上記に譲りますが、次節で、できることをざっとご紹介します。

Lombokでできる自動生成

紹介するのはLombok 1.14.8の標準機能です。まだ不安定な実験的機能は除いてます。

アノテーションをつけるだけで、さまざまなボイラープレートが自動生成されます。それぞれのアノテーションのパラメータで細かい制御もできます。

簡単な評点

数が多いので、流し読みしやすいように、独断と偏見で4段階評価をつけていきます(ぼくのJavaの使い方によるものなので、あくまで目安とお考えください)。それぞれ、こんな意味です。

★★★
よく使う。絶対覚えたい!!!
★★☆
まあまあ使いそうなので覚えておこう!
★☆☆
ほとんど使わなそう。必要なときに調べて使えればいいかな。
☆☆☆
お役御免。Java自体にその機能が入ったとか、お仕事的に…などの理由で。
Lombokの標準機能

こんなことができます。

@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
★★★
コンストラクタの自動生成。
@Getter
@Setter
★★☆
アクセサ・メソッドの自動生成。
@EqualsAndHashcode
@ToString
★☆☆
すべてのオブジェクトに共通するメソッド、equals/hashcode/toStringの自動生成。
@Data
★★★
シンプルなPOJOやJavaBeanの自動生成。 一緒に使うことが多い@RequiredArgsConstructor, @Getter, @Setter(finalでないフィールドのみ), @ToString, @EqualsAndHashCodeをセットでやってくれます。
@Value
★★★
イミュータブルなクラスの自動生成。@Dataと同じく、@RequiredArgsConstructor, @Getter, @ToString, @EqualsAndHashCodeをセットでやってくれます。すべてのフィールドがfinalなので、セッタ・メソッドはありません。クラスもfinalになるので、サブクラスでミュータブルなフィールドが定義されることもなくなります。
@Cleanup
☆☆☆(Java 6までだったら★★★
try文での自動リソース管理。Java 7にtry-with-resource文が追加されことで役目を終えましたが、Java 6までの時代では助かりました。
@SneakyThrows
★★☆
メソッド内で発生した、処理しようがない&伝播もさせたくない検査例外を、非検査例外に自動変換してくれます。例外をどう扱うべきかに注意が必要ですが、非検査例外にしたくなるときはしばしばあります。
@Synchronized
★☆☆
メソッドをsynchronizedメソッドへ自動変換。ぼくは普段の開発ではほとんど並行処理を書かないので、使うことは少なそうです。
@Log
★☆☆
ログ出力に使うLoggerフィールドの自動生成。これもSpringなどを使ってログ処理を織り込んでいるので、ぼくにはあまり使いみちはなさそうです。

またアノテーションではなく、新たなキーワード val も追加されます。valを使うと、変更不能なローカル変数を簡単に書くことができます。具体的には、ローカル変数の型名とfinal修飾子を省略することができます。Scalaのvalや、C#のvarなど、他のプログラミング言語にある機能をJavaで使えるようになります。見た目は、

// final String name = "hoge"; と同じ
val name = "hoge";

キーワードまで追加できてしまえるなんて! 将来のJava言語でも、こういう便利なキーワードが追加されるのを期待したいです。ただ、IDEによって使えたり使えなかったりするようです(IDEAでは使えませんでした……)。

Lombokが提供してくれる自動生成、なかなか便利なものが揃っているのではないでしょうか。

次節からは、このLombokにさらに機能を加えたLombok-pgを紹介します。

Lombokにさらにひと味加えたLombok-pg

peichhorn/lombok-pgは、Philipp Eichhornさんによって開発された、Lombokの機能拡張版です。

名前のpgには、Lombokに追加したい実験的機能の遊び場(PlayGround)という意味があるそうです。Lombokのイメージが赤唐辛子なのに対し、実験版であるLombok-pgは青唐辛子(熟してない)ところも象徴的です。

残念ながら、Lombok-pgの開発は2012年を最後に途絶えており、ドキュメントも中断しています(Philippさん自身からは開発終了の音沙汰もないようですが……)。

しかし幸いにも、Lombok-pgはLombok本体の開発コミュニティでも注目されているようで、Lombok-pgで追加された機能を本体にマージする動きが始まっています。実際、いくつかの機能が、まだ実験的機能という位置づけではありますが、既に取り込まれているようです。

次節からは、Lombok-pgでどんな機能が追加されたかを見ていきます。

Lombok-pgが追加した機能

Lombok-pg 0.11.3 の機能の数々を簡単に分類し、ざっと紹介していきます。

この記事を書くために一通り触ってみたのですが、残念ながら2012年に開発が中断しているため、JavaのバージョンやIDEによっては使えない機能もチラホラありました。Java 8とIDEAで動くようにforkしたいなぁ……。

メソッドに機能を織り込むアノテーション

これらは、自分が書いたメソッドにコードを追加して、何らかの機能を付与してくれるものです。

@Rethrow
@Rethrows
★★☆
無視したい検査例外を非検査例外に変換してくれるLombok本体の@SneakyThrowsの亜種で、変換する際に例外にセットするメッセージを設定したり、どの非検査例外に変換するかを指定したりできます。
@Rethrowsは、複数の@Rethrowを束ねることで、より複雑な例外の変換規則を定義するものです。
@Validate.With
@Validate.NotEmpty
@Validate.NotNull
★★☆
メソッドやコンストラクタの引数に対するバリデーション処理の呼び出しを自動生成してくれます。バリデーション処理とは、引数が前提条件(nullではない、など)を満たしているかを検査し、満たしていなければ例外を投げるような処理です。
@Sanitize.With
@Sanitize.Normalize
★☆☆
メソッドやコンストラクタの引数に対するサニタイズ処理の呼び出しを自動生成してくれます。サニタイズ処理とは、値から望ましくない部分(例えば特定のTMLタグなど)を除去するような処理です。
@WriteLock
@ReadLock
★☆☆
並行処理のためのものです。メソッドを、@Synchronizedよりも粒度の細かい(syncrhonizedより性能が高まりやすい)ロックを行うように自動変換してくれます。Java 5で導入されたConcurrency FrameworkのReadWriteLockが使われます。ReadWriteLockの使用は、従来のsynchronized修飾子やsynchronized文よりもかなり手間でしたが、これが簡単に書けるようになります。
@Await
@Signal
@AwaitBeforeAndSignalAfter
★☆☆
これも並行処理のためのものです。メソッドを、何らかの条件が満たされるまで待機(Await)したり、その条件を満たした信号(Signal)を送るように自動変換してくれます。
@SwingInvokeLater
@SwingInvokeAndWait
☆☆☆
メソッドを、AWTのEventQueueをチェックして実行タイミングを変えるようにしてくれます。
メソッドを自動生成するアノテーション

これらは、お決まりのメソッドを自動生成してくれるものです。

@AutoGenMethodStub
★☆☆
メソッドのスタブ(仮実装)を自動生成してくれます。あるインタフェースのメソッドのうち、実装したいものだけを実装すればよくなります。
Java 8では、スタブや基本実装をインタフェースに定義できる言語機能デフォルト・メソッドが入りました。しかし、デフォルト・メソッドではないメソッドがたくさんあるインタフェースを実装するときには、このアノテーションは役立ちそうです。
@BoundSetter
★☆☆
@Setterの亜種で、値をセットしたときに何らかの処理を挟み込む特別なセッタ・メソッドを生成してくれます。セットの監視には、java.beans.PropertyChangeListenerで行います。これは関数型インタフェース
@Builder
@Builder.Extension
★★★
「生成時にたくさんのパラメータが必要なオブジェクト」などの生成を助けるBuilderパターンが使えるクラスを自動生成してくれます。Builderオブジェクトのメソッド名に”with”などの接頭詞をつけたり、指定してもしなくてもよいパラメータのデフォルト値を決めたりなど、さまざまな制御ができます。
これはLombok本家でも実験的機能として取り込まれています。
@Singleton
★★☆
指定したクラスのシングルトンインスタンスとそれを取り出すstaticメソッドを自動生成してくれます。ただし、コンストラクタをprivateにするなどまではしてくれません。
@EnumId
★★★
「列挙型のインスタンスそれぞれを識別できる値を、対応するインスタンスに変換するファクトリメソッド」、簡単にいうと「1→HEART, 2→SPADE、3→DIAMOND、4→CLUB、5→JOKERみたいな変換をやってくれるメソッド」を自動生成してくれます。Stringやintで扱っていた「なんとか区分」を列挙型に変換するということは、なかなかの頻度であるので、便利です。
Lombok本家にはまだ実験的機能にもなっていませんが、取り込もうという議論があるようです。
@FluentSetter
★☆☆
@Setterの亜種で、戻り値がvoidでなくインスタンス自身なメソッドを自動生成してくれます。これにより、メソッドチェーンができるようになります。
@Accessorsという、より汎用的で機能を今後追加しやすい名前で、Lombok本家でも実験的機能として実現されています。
@LazyGetter
★★☆
@Getterの亜種で、「最初にゲッタが呼び出されたときに初めてフィールドの値を生成し、以降はその値を返す」ゲッタ・メソッドを自動生成してくれます。値の生成が重たい処理である場合に、値が必要になるまで生成を後回しにすることで、性能向上を期待できます。
@ListenerSupport
★☆☆
AWTなどのリスナを登録したり、発火したりするコードを自動生成してくれます。
@VisibleForTesting
★☆☆
クラスやメソッドを、テストコード(呼び出し元のクラス名が”Test”で終わる)からはpublicで見えるように変換してくれます。クラスやメソッドの可視範囲をテスト目的でゆるめざるをえない時に使えそうです。
@Warning
★☆☆
ちょっと特殊で、コードを生成するのではなく、コンパイル時に任意の警告メッセージを表示させることができます。このアノテーションをつけたクラスやメソッドに関して、開発者に何らかの注意(まだこういうバグがあるのでこのURLを見てね、とか)を喚起したい場合に便利そうです。
@Action
@Function
@Predicate
☆☆☆(Java 7とそれ以前なら★☆☆
メソッドを任意の関数型インタフェースのオブジェクトにカプセル化します。関数型インタフェースとして、LombokはAction0(引数なし)~Action8(8引数)、Function0~Function8、Predicate0~Predicate8を提供しています。他の関数型インタフェースを指定することもできます。
Java 7までで関数型インタフェースを活用したいときには少し便利だったかもしれません。しかしJava 8では、メソッドを関数型インタフェースのインスタンスに変換する言語機能メソッド参照が導入されたので、出番はなくなりそうです。
言語機能の追加

lombok本家の val のように、Java言語には今のところない言語機能を追加してしまうものです。Javaの常識からかけ離れてしまうので、気をつけて使う必要がありそうです。

残念ながらどれもIDEとの相性が悪く、使用性に難点があります。動くようにforkしたいところです。

@ExtensionMethod
★★★
C#にあるような言語機能 拡張メソッドが使えるようになります。ここでいう拡張メソッドとは 「あたかも第1引数のインスタンスメソッドかのように呼びだせる、staticメソッド」です。これによって、自分でメソッドを追加することのできないクラスやインタフェース……(例えばStringや配列、Iterableなど)……にインスタンスメソッドを追加したかのようなコードが書けます。
例えば、
public class ExtensionMethods {
    public static <T> T orElse(T thisValue, T defaultValue) {
        return thisValue != null ? thisValue : defaultValue);
    }
}

@ExtensionMethod(ExtensionMethods.class)
public class ExtensionMethodsClient {
    void fooMethod(String maybeNull) {
        // staticメソッドorElseが、あたかもインスタンスメソッドかのように呼び出せる
        final String foo = maybeNull.orElse("NULL!!");

        // fooで何かいいことをする
    }
}
みたいに、nullもそこそこ安全に扱えるようになります。他には、Java 8だと配列をStreamに変換するのはStreamクラスやIntStreamクラスなどのstaticメソッドを呼ぶ必要がありますが、それも
array.stream().map(e -> e.foo());
などと書けちゃうわけです。ジェネリクスにもそこそこ対応してます。これはヤバイです。マジヤバです。色々おもしろそうなことができそうです。
Lombok本家でも実験的機能として取り込まれています。
早く主要なIDEで使えるようになって欲しいです……。
yield
★☆☆
いわゆるジェネレータという言語機能をエミュレートしてくれます。Lombok-pgによって生成されるジェネレータは、Iterableのインスタンスとなります(yiledメソッドを使用しているメソッドが、適切に状態遷移して値を生成(ジェネレート)するIterableへと、変換されます)。Lombok-pgが生成するジェネレータのコードは、かなり上手く状態管理されるようです。try-finally文にも対応し、適切にfinally節が実行されます。すごいです。
独自のIterableとIteratorを定義したくなることは稀にあるので、yieldが使えると結構うれしいのですが……。
tuple
☆☆☆
名前はタプル(値の組)ではありますが、今のところ、いわゆるdestructuring(分配束縛)という言語機能をエミュレートするだけのようです。これだけでは、あまり使いみちがないような気がしますが、どうなんでしょう。

最後に

LombokとLombok-pgを使うことで、Javaのボイラープレートの数々を減らせることがお分かりいただけたかと思います。

LombokはJava 8のプロジェクトでも大活躍してくれてるのですが、Lombok-pgはJava 8やIDEで動かない機能がけっこうあるのがもったいないなぁ……。せっかくなので、動くように修正してみようかと思います。どんな風にコードを生成したり、変形したりしているかを理解して、Javaのイライラするボイラープレートを減らせるような新機能を作れるようになりたいですね。

人気の投稿