きり丸の技術日記

技術検証したり、資格等をここに残していきます。

【Java】lombokの便利な使い方(Builder編)

最近、レベルアップのためにEffective Javaの第3版を読むようになりました。

そのなかで、「第2章 項目2 多くのコンストラクタパラメータに直面した時にはビルダーを検討する」を読んだ時にこう思いました。

「意外とBuilderって知られてない?」

なので、lombokで簡単に付与できるBuilderを紹介する記事を書きました。

なお、タイトルに〇〇編と書いてますが、Builder編以外は需要がなさそうなので書かないと思います。

lombokとは


getterやsetter等を自動生成してくれるライブラリ。かなり便利。

VSCodeやIntellij Ideaはプラグインを入れるだけで導入できる。
Eclipseはちょっと手間がかかった気がします(今はどうだろう…)

私はlombokで特に以下のアノテーションを使ってます。
詳しいことは調べてください。

他のアノテーションもいいよ!
っていうのがあれば教えてくれると嬉しいです。

  • Data
  • Value
  • Builder
  • NoArgsConstructor
  • AllArgsConstructor
  • RequeiredArgsConstructor

使い方


クラスにつけるだけで使えるようになります。
使い方も簡単です。

// 使えるようにする方法
@Builder // クラスにつけるだけ!
class Book{
    private final String isbn; // ISBN
    private final String author; // 筆者
    private final int money; // 価格
}
// 使い方
Book.builder()
.isbn("XXXXX")
.author("きり丸")
.money(500)
.build();

下記と一緒のものが生成されます。

// コンストラクタでの生成するパターン
new Book("XXXXX", "きり丸", 500);

// or

// setterで後で設定するパターン
Book book = new Book();
book.setIsbn("XXXXX");
book.author("きり丸");
book.money(500);

ビルダーの強みとは


知らない人は他の言語でも使えるから知ってほしい。

コンストラクタでは


コンストラクタの場合、パラメータを設定する順番を意識する必要があります。
しかも、パラメータの順番を変えたい、増やしたいという場合にも弱いです。

開発者が一人ならまだしも、チーム開発で急にパラメータの順番を変更されたときは修正が大変です。
全項目がStringになっている場合は、最後まで気づけないことも…。

// 変更前
class Book{
    private final String isbn; // ISBN
    private final String author; // 筆者
    private final LocalDateTime end; // 絶版した時期
    private final LocalDateTime start; // 販売開始時期
    private final int money;  // 値段
}
// 変更後(start/endの順番変更と編集者追加)
class Book{
    private final String isbn; // ISBN
    private final String author; // 筆者
    private final String editor; // 編集者
    private final LocalDateTime start; // 販売開始時期
    private final LocalDateTime end; // 絶版した時期
    private final int money;  // 値段
}
LocalDateTime now = LocalDateTime.of(2020, 1, 2, 3, 4, 5);

// 修正前のコンストラクタの場合
new Book("978XXXXXXXXXX", "きり丸", null, now, 500);

// 修正後のコンストラクタの場合(順番の変更も意識しないといけない)
new Book("978XXXXXXXXXX", "きり丸", "乱太郎", now, null, 500);

ビルダーでは


ビルダーの場合は、項目を選んで値を設定します。
なので、パラメータの順番が変わったり、増えた場合でも目的通りの値を設定できます。

変数が2,3個しかないときはコンストラクタでもいいですが、変数が増えたときのことを考えて最初からBuilderで作っておくと楽です。

// 修正前のBuilderでの生成
Book.builder()
.isbn("978XXXXXXXXXX")
.author("きり丸")
.end(null)
.start(now)
.money(500)
.build();
// 修正後のBuilderでの生成
Book.builder()
.isbn("978XXXXXXXXXX")
.author("きり丸")
.editor("乱太郎") // この行追加だけ!
.end(null)
.start(now)
.money(500)
.build();

Builderの有効的な使い方


私は異常系テストを行うときもBuilderパターンで作ることが多いです。
正常系との相違点が非常にわかりやすいので、何が違うのかが分かりやすくなります。

他にも、この生成オブジェクトを元にINSERTする等のロジックを組むと、SQLのWHERE文の差分を簡単に確認できるようになります。

setterだと戻り値がvoid型になり、ワンライナーで書けないので意識が散りがちです。

// Builder型で正常系のデータを返却する
public Book.Builder createSuccess(){
    return Book.builder()
    .isbn("978XXXXXXXXXX")
    .author("きり丸")
    .editor("乱太郎")
    .start(now)
    .end(null)
    .money(500);
}

// 使用する直前で、buildして異常系のデータを生成する。
Book success = createSuccess().build();
Book notIsbn = createSuccess().isbn("あいうえお").build();
Book notAuthor = createSuccess().author(null).build();
...

コンストラクタで生成した場合、差分が一目ではわかりません。
notAuthorの桁数が落ちているのにも気づけない。

テストのコツは、できるだけ意識するポイントを減らすことだと考えているので、パラメータの数だけ意識が散るのはNGです。

Book success = new Book("978XXXXXXXXXX", "きり丸", "乱太郎", now, null, 500);
Book notIsbn = new Book("あいうえお", "きり丸", "乱太郎", now, null, 500);
Book notAuthor = new Book("978XXXXXXXXX", null, "乱太郎", now, null, 500);
...

setterは悪くないけど、オブジェクト生成と違う行でセットするので、他にも値を変更している可能性を拭えない。

// setterの場合
Book notStart = createSuccess().build();
notStart.setStart(null);
// 以降で setAuthor(null)とかするのでは…?と意識が散る。

Builderのデメリット


生成するときの行数が増えます。

あと、明らかに1つの属性しか持たないのであれば、コンストラクタの方が見やすいです。

Book book = new Book();

Book book = Book.builder().build();

終わりに


lombokで簡単に使える割には、他の言語でも有効なパターンなのでぜひ覚えて欲しいです。

テスト時にBuilder型で値をセットアップするのを思いついたときは、自分事ながら感動しました。

ぜひ、使ってみてください。


もしこの記事が役に立ったのであれば、はてぶ、Twitterでの記事の拡散、twitterのフォローもよろしくお願いします。

私の励みになります。