Lombok で Spice up your Java!

Pocket

こんにちは、鈴木です。

TECHSCORE Advent Calendar 2015 の 2 日目の記事です。

今回は Lombok という Java のライブラリをご紹介します。アノテーション一つで単純なアクセッサメソッドを自動生成してくれるなど、何度も書かなければならない「お決まりのコード」を減らしてくれるライブラリです。

この記事のタイトルに含んでいる「Spice up your Java」は Lombok のサイトに書かれていたものです。その言葉通り、ピリリとするスパイスをひと振りするように、普通の Java コードに一味添えてくれます。

それでは以下の順番でお話しします。

 

お決まりのコード

冒頭で「お決まりのコード」と言いましたが、英語では「ボイラープレートコード(Boilerplate Code)」と呼ぶそうです。具体的には以下のようなコードです。

  • 単純に値の取得/設定を行うだけのアクセッサメソッド
  • 引数無しのコンストラクタ、final なフィールドの値を全て受け取るコンストラクタ
  • toString() をオーバーライドするコード
  • equals(), hashCode() をオーバーライドするコード
  • 特定の例外を catch して RuntimeException として投げ直すコード
  • 例外を投げるだけの private なコンストラクタ(java.lang.Math のように static メソッドを集めたクラスの場合)
  • etc..

どれも必要ではあるものの、毎回同じようなコードを書かなければならないことも多く、正直なところ面倒だと感じてしまいます。

使用例: これが、こうなる!

Lombok の機能はいくつもあるのですが、まずは一例をご覧ください。

これが、

こうなる!

なんということでしょう。

大量の「お決まりのコード」が @Data というアノテーション一つに置き換えられてしまいました。

お決まりのコードの作り方とタイミング

「Lombok を使うとお決まりのコードを生成してくれるので便利」という趣旨の記事を書いているわけですが、ここで「お決まりのコード」の作り方とタイミングにどのようなパターンがあるのか整理しておきましょう。Lombok が唯一の選択肢ではないということと、他の方法を理解した上で Lombok を評価することが大切だと思うからです。

お決まりのコードの作り方: 自力 or 自動生成

大きく分けると「自分で書く」か「ツール等に自動生成してもらう」のどちらかになります。いくつか具体例を挙げてみます。

  • 本当に自力でタイプする
  • エディタのコードスニペット機能を使う
  • IDE のコード生成機能を使う
  • 専用のコードジェネレータを使う
  • Lombok を使う
  • etc..

他にも色々あると思いますが、次は「お決まりのコード」が作られるタイミングを分類してみます。

お決まりのコードを作るタイミング: コーディング時 or コンパイル時

タイミングは大きく分けると「コーディング時」か「コンパイル時」になります。実行時に生成するという選択肢もありますが、ここでは考えないことにします。(実行時にアクセッサメソッドを作られてもコンパイル時点では参照できないためです。)

コーディング時にコード生成する方法には、IDE のコード生成機能を使う方法などがあります。メリットは生成されたコードを確認しやすいところです。デメリットは変更に弱いところです。例えばフィールド名の変更に伴いアクセッサメソッドを再生成する場合、既存のアクセッサメソッドを手作業で削除しなければなりません。

コンパイル時にコード生成する方法には、一部のコードジェネレータや Lombok  があります。メリットは変更にやや強いところで、フィールド名を変更すれば自動的に生成されるアクセッサメソッドの名前も変更されます。デメリットは意図しないコードが生成されていても気付きにくいところです。

どちらが優れているということはないので、やりたいことに合った手段を選びましょう。

Lombok の機能

それでは Lombok の機能をいくつかご紹介します。(詳細は Lombok のサイトをご覧ください。)

 

val (lombok.val)

val は final な変数を定義するときに使います。

Before:

After:

変数の型は右辺の値と同じになります。

もう一度言いますが、変数の型は右辺の値と同じになります。オブジェクト指向の教科書に「変数の型は ArrayList のような具象クラスではなく、List のようなインタフェースにしましょう。」と書かれていることもありますが、その教えを知らぬ間に破ってしまわないように注意しましょう。

以下のコードをみてください。

これは次のコードに変換されます。

この場合は、(1) 気にしない、(2) キャストする、(3) val を使わずに普通に書く、のいずれかを選びましょう。

もう一点注意ですが、Lombok 1.16.6 では、Lambda 式の中で val を使った場合にコンパイルエラーとなってしまいます。稀ですが Lambda 式の外でもコンパイルエラーになったこともあるので、若干安定性に欠けています。「それでは val は使い物にならないのか?」と問われると、「そこまで悪くはない」というか「うまく動かないケースは避けながらお付き合いすることはできる」という感じです。

 

@ToString (lombok.ToString)
@EqualsAndHashCode (lombok.EqualsAndHashCode)

@ToString はフィールドの値を含んだ文字列を返す toString() を生成します。@EqualsAndHashCode はフィールドの値を用いて比較を行う equals() とフィールドの値から生成したハッシュ値を返す hashCode() を生成します。

Before:

After:

 

@Value (lombok.Value)
@Data (lombok.Data)

@Value はいわゆる Immutable なクラス(値を変更できないクラス)、@Data は Mutable なクラス(値を変更できるクラス)を定義するときに使用します。使い方はクラスにアノテーションを付けるだけです。

以下に @Value と @Data の違いをまとめます。

@Value が付けられたクラス:

  • final なクラスになる(=継承できないクラスになる)
  • Getter メソッドが生成される
  • toString(), equals(), hashCode() が生成される
  • 全フィールドが final になる
  • 全フィールドの値を受け取るコンストラクタが生成される

@Data が付けられたクラス

  • Getter 及び Setter メソッドが生成される
  • toString(), equals(), hashCode() が生成される
  • final なフィールドの値を受け取るコンストラクタが生成される

 

@Getter (lombok.Getter)
@Setter (lombok.Setter)

@Getter は Getter メソッド、@Setter はSetter メソッドを生成します。アノテーションはクラスまたはフィールドに付けることができます。クラスに付けた場合は全てのフィールド、フィールドに付けた場合はそのフィールドのみが対象となります。アクセスレベルを指定することもできます。

使用例(フィールドに @Getter を付ける場合):

使用例(クラスに @Getter を付ける場合):

使用例(アクセスレベルを指定する場合):

 

@NoArgsConstructor (lombok.NoArgsConstructor)
@RequiredArgsConstructor (lombok.RequiredArgsConstructor)
@AllArgsConstructor (lombok.AllArgsConstructor)

どれもクラスに指定するアノテーションです。@NoArgsConstructor は引数無しコンストラクタ、@RequiredArgsConstructor は final なフィールドの値を受け取るコンストラクタ、@AllArgsConstructor は全フィールドの値を受け取るコンストラクタを生成します。

使用例:

 

@NonNull (lombok.NonNull)

@NonNull は null チェックを行うコード(値が null なら例外を投げるコード)を生成します。

@NonNull はフィールドに対して指定することもでき、@Data や @Setter, @AllArgsConstructor などと組み合わせると便利です。以下は @Data と @AllArgsConstructor を組み合わせた例です。

@Data と @AllArgsConstructor によってコンストラクタや Setter メソッドが生成されます。そして、フィールドに @NonNull が付けられている場合、生成されるコンストラクタや Setter メソッドに null チェックのコードが挿入されます。上記のコードは次のようになります。

 

@Cleanup (lombok.Cleanup)

@Cleanup は close() メソッドを呼び出すコードを生成します。

Java7 以上の場合は try-with-resources 文でも同等のことができるので、どちらを使っても良いでしょう。

 

@SneakyThrows (lombok.SneakyThrows)

@SneakyThrows を使うとチェック例外を非チェック例外であるかのように扱えるようになります。・・・言葉だけの説明だと伝わりにくいと思いますので、まずは @SneakyThrows を用いたコードを見てください。

Thread.sleep() はチェック例外である InterruptedException を投げるので、普通だと try-catch で囲むか、Thread.sleep() を呼び出しているメソッドの throws に InterruptedException を追加することになります。しかし、@SneakyThrows を使うと上記のように書くことができます。

それでは InterruptedException はどこへ行ってしまうのかというと、どこかへ消えて無くなるわけではありません。Thread.sleep() の中で InterruptedException が発生した場合は、そのまま sleepOneSecond() メソッドの上位階層に伝えられていきます。非チェック例外のように扱われるというだけです。

この「非チェック例外のように」の部分に魔法じみたものを感じると思いますので、その部分を深掘りしてみましょう。まず、上記コードは次のコードに変換されます。

単純に try-catch で囲まれ、Lombok.sneakyThrows() に catch した例外を渡しています。するとチェック例外が非チェック例外のように生まれ変わって throw される・・。その秘密は Lombok.sneakyThrows() にあるようです。ということで、コードを見ると次のように実装されていました。

例外は sneakyThrows() から sneakyThrows0() に渡され、その中で throw しているだけでした。

調べてみたところ、チェック例外と非チェック例外の区別はコンパイル時だけのものであり、JVM レイヤー(実行時)では区別されないようです。JVM レイヤーで区別されないというのは、チェック例外だろうと非チェック例外だろうと JVM からすれば「例外を投げる」という同じ処理だということです。それは、コンパイル時のチェックさえすり抜けることができればチェック例外を非チェック例外のように throw することができる、ということです。そして、Generics を使うことでコンパイル時のチェックをすり抜ける、という仕掛けです。

 

@Log (lombok.extern.java.log.Log)
@Log4j (lombok.extern.log4j.Log4j)
@Log4j2 (lombok.extern.log4j.Log4j2)
@Slf4j (lombok.extern.slf4j.Slf4j)
@XSlf4j (lombok.extern.slf4j.Xslf4j)
@CommonsLog (lombok.extern.apachecommons.CommonsLog)

アプリケーションからログ出力をするときに、以下のような private static final な Logger を定義することは良くあると思います。

@Log を使うと以下のように書くことができます。上記の「private static final な Logger を定義する」という部分をアノテーションに置き換えることができます。

@Log 以外にも @Log4j や @Slf4j など、各種 Logger に対応したアノテーションが用意されています。

まとめ

いかがでしたでしょうか。Java を使っていると「手軽にメタプログラミングができるスクリプト言語って楽だなー」と思うことが多いですが、Lombok を使えばそれと似たようなことが実現できます。

Lombok が提供するアノテーションをいくつも紹介しましたが、中には設定ファイルで動作をカスタマイズできるものもあります。例えばアクセッサメソッドの名前に get/set を付けるかどうかを制御することもできます。実験的な機能(まだ正式版ではない機能)の中にも面白そうなものがあります。是非とも Lombok をお試しください。

 

Pocket

Advent Calendar 2015の連載記事

  1. TECHSCORE Advent Calendar 2015
  2. Redshift と PostgreSQL に同時に JDBC 接続する
  3. Lombok で Spice up your Java!
  4. 画像を指定するだけ!非デザイナーでも簡単にそれっぽい配色ができるツールを作ってみた
  5. 新卒文系エンジニアの記録:配属半年間の失敗を振り返ってみた
  6. 非同期処理のすすめ
  7. ioDrive2の導入で支える、そのIOPS - 導入検討編.
  8. GoでパイプラインからSlackに通知する
  9. fuse でオレオレファイルシステムを作ってみた (Haskell で)
  10. Erlang はじめました
  11. ちょっと地味なビルドとリリースの話 (レガシーシステム改革、はじめの一歩)
  12. Java8 最速 boolean[] to Stream 選手権
  13. Google Apps の Directory API にてWebブラウザを介さずに認証する
  14. 風データをビジュアルに表現する
  15. マイクロフレームワーク「Ninja」を使ってみる
  16. 赤ちゃんvimmerからよちよちvimmerにクラスチェンジを果たすためのTips
  17. PostgreSQL FDW を作ってSQLでログ検索してみた
  18. Goで偽名ジェネレータを作りました
  19. 書き込み中に削除されたファイルを救出する
  20. 運用情報更新のススメ
  21. ちゃんと読んでくれましたか?
  22. Presto コネクターを実装する 第三回
  23. Ruby2.3を触ってみる
  24. Git 困ったときのtips集
  25. 5分で読む入門編:Java 8 ラムダ式 コレクション編(2)リストの検索
  26. CloudFront (+ S3) + JWPLAYER で様々なデバイスのブラウザから動画をストリーミング再生する

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です