LoginSignup
2
3

Jilt使い方メモ

Last updated at Posted at 2024-06-16

Jiltとは

  • ビルダークラスをアノテーションプロセッサーで自動生成する Java ライブラリ
  • 単純にソースコードを生成する仕組みなので、実行時に依存関係は不要となり、バイトコードをいじるといった黒魔術も使わない
  • 必須プロパティを強制できる形式のビルダークラス(Staged Builder)を生成できる

環境

IDE: IntelliJ IDEA 2024.1.3 (Community Edition)

Gradle
> gradle --version

------------------------------------------------------------
Gradle 8.8
------------------------------------------------------------

Build time:   2024-05-31 21:46:56 UTC
Revision:     4bd1b3d3fc3f31db5a26eecb416a165b8cc36082

Kotlin:       1.9.22
Groovy:       3.0.21
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.3 (Eclipse Adoptium 21.0.3+9-LTS)
OS:           Windows 10 10.0 amd64
Maven
> mvn --version
Apache Maven 3.9.7 (8b094c9513efc1b9ce2d952b3b9c8eaedaf8cbf0)
Maven home: C:\pf\maven\latest
Java version: 21.0.3, vendor: Eclipse Adoptium, runtime: C:\pf\java\latest
Default locale: ja_JP, platform encoding: UTF-8
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

Hello World

IntelliJ の設定

image.png

  • File > Settings を開き、 Build, Execution, Deployment > Compiler > Annotation Processors の Enable annotation processing のチェックをオンにする

実装

build.gradle
plugins {
    id "java"
}

java {
    sourceCompatibility = 21
    targetCompatibility = 21
}

compileJava.options.encoding = "UTF-8"

repositories {
    mavenCentral()
}

dependencies {
    compileOnly "cc.jilt:jilt:1.6"
    annotationProcessor "cc.jilt:jilt:1.6"
}

Maven の場合

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>example</groupId>
    <artifactId>jilt</artifactId>
    <version>1.0.0</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cc.jilt</groupId>
            <artifactId>jilt</artifactId>
            <version>1.6</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

依存関係を provided で設定する。

Hoge.java
package sandbox.jilt;

import org.jilt.Builder;

@Builder
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
Main.java
package sandbox.jilt;

public class Main {

    public static void main(String[] args) {
        final Hoge hoge = HogeBuilder.hoge().text("Hello Jilt!!").number(9).build();
        System.out.println(hoge);
    }
}
実行結果
Hoge{text='Hello Jilt!!', number=9}

説明

build.gradle
dependencies {
    compileOnly "cc.jilt:jilt:1.6"
    annotationProcessor "cc.jilt:jilt:1.6"
}
  • compileOnlyannotationProcessorcc.jilt:jilt を設定する
Hoge.java
import org.jilt.Builder;

@Builder
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
  • ビルダークラスを作りたいクラスに @Builder アノテーションを付ける
  • フィールドの宣言順序と同じ順番で引数を受け取るコンストラクタを定義しておく
HogeBuilder.java
package sandbox.jilt;

import java.lang.String;
import javax.annotation.processing.Generated;

@Generated("Jilt-1.6")
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • プロジェクトをビルドすると、 build/generated/sources/annotationProcessor/java/main の下に HogeBuilder.java が出力される
  • Maven の場合は target/generated-sources/annotations の下に出力される
Main.java
        final Hoge hoge = HogeBuilder.hoge().text("Hello Jilt!!").number(9).build();
  • 出力された HogeBuilder を使って Hoge のインスタンス生成を書くことができる

Builder

クラスに @Builder を付ける

Hoge.java
import org.jilt.Builder;

@Builder
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • クラスに @Builder アノテーションを付けると、そのクラスに宣言されている全てのフィールドを受け取れるビルダーが生成される
  • build メソッドの中でビルダーが呼び出すコンストラクタは、フィールドの宣言と同じ順序で引数が並んでいることを想定している
    • ここでは String text > int number の順でフィールドが定義されているので、コンストラクタ引数も同じ順序で並んでいることが期待される
    • 順序が異なるとコンパイルエラーになったり、型が同じだけど異なる値が渡されてしまう恐れがある

対象外のフィールドを指定する

Hoge.java
import org.jilt.Builder;

@Builder
public class Hoge {
    private final String text;
    private final int number;
    @Builder.Ignore
    private boolean bool;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • @Builder.Ignore アノテーションをフィールドにつけると、そのフィールドは除外される

コンストラクタに @Builder を付ける

Hoge.java
import org.jilt.Builder;

public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }

    @Builder
    public Hoge(String text) {
        this(text, 999);
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public Hoge build() {
    return new Hoge(text);
  }
}
  • 特定のコンストラクタに @Builder を設定することで build メソッドで呼び出すコンストラクタを指定できる
  • この場合、ビルダーで設定できるプロパティはコンストラクタで渡しているモノだけに絞られる

static メソッドに @Builder を付ける

Hoge.java
import org.jilt.Builder;

public class Hoge {
    private final String text;
    private final int number;

    @Builder
    public static Hoge create(String text, int number) {
        return new Hoge(text, number);
    }

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
package sandbox.jilt;

...

public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return Hoge.create(text, number);
  }
}
  • static メソッド(ファクトリメソッド)に @Builder を付けることができる
  • この場合、ファクトリメソッドが build メソッドの中から呼ばれる形になる
  • この方法は最も柔軟にインスタンスを生成できる手段で、ファクトリメソッドさえ用意すればライブラリが提供するクラスのビルダーを作ることもできるようになる

ビルダーのクラス名を指定する

Hoge.java
import org.jilt.Builder;

@Builder(className = "MyHogeFactory")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
  • className で生成されるビルダークラスの名前を指定できる
  • 未指定の場合のデフォルトは、生成対象のクラス名の後ろに Builder がついたものになる

出力先のパッケージを指定する

Hoge.java
@Builder(packageName = "sandbox.jilt.builders")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
package sandbox.jilt.builders;

import java.lang.String;
import javax.annotation.processing.Generated;
import sandbox.jilt.Hoge;

@Generated("Jilt-1.6")
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • @BuilderpackageName でビルダーの出力先パッケージを指定できる
  • 未指定の場合のデフォルトは、生成対象のクラスと同じパッケージになる

セッターメソッドのプレフィックスを指定する

Hoge.java
@Builder(setterPrefix = "set")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder setText(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder setNumber(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • setterPrefix で、ビルダーのセッターメソッドのプレフィックスを指定できる
  • デフォルトは空で、セッターメソッドはフィールド名と同じになる

ビルダーのファクトリメソッド名を変更する

Hoge.java
@Builder(factoryMethod = "newBuilder")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder newBuilder() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • factoryMethod で、ビルダーのインスタンスを最初に生成するためのファクトリメソッド名を変更できる
  • 未指定の場合のデフォルトは、生成対象のクラス名の先頭を小文字にしたものになる

ビルドメソッド名を変更する

Hoge.java
@Builder(buildMethod = "create")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  private int number;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge create() {
    return new Hoge(text, number);
  }
}
  • buildMethod で、最後にインスタンスを構築して返すビルドメソッドの名前を変更できる
  • 未指定の場合のデフォルトは build

既存のインスタンスからビルダーを生成する

Hoge.java
@Builder(toBuilder = "from")
public class Hoge {
    final String text;
    private final int number;
    private final boolean bool;

    public Hoge(String text, int number, boolean bool) {
        this.text = text;
        this.number = number;
        this.bool = bool;
    }

    public int number() {
        return number;
    }

    public int getNumber() {
        return number;
    }

    public boolean isBool() {
        return bool;
    }
}
HogeBuilder.java
public class HogeBuilder {
  private String text;

  private int number;

  private boolean bool;

  public static HogeBuilder hoge() {
    return new HogeBuilder();
  }

  public static HogeBuilder from(Hoge hoge) {
    HogeBuilder hogeBuilder = new HogeBuilder();
    hogeBuilder.text(hoge.text);
    hogeBuilder.number(hoge.number());
    hogeBuilder.bool(hoge.isBool());
    return hogeBuilder;
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public HogeBuilder bool(final boolean bool) {
    this.bool = bool;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number, bool);
  }
}
final Hoge hoge = HogeBuilder.hoge().text("a").number(10).bool(true).build();
final Hoge copy = HogeBuilder.from(hoge).number(90).build();
  • toBuilder を設定すると、既存のインスタンスを元に新しいビルダーを生成するメソッドが追加される
    • ここに指定した値が、そのメソッドの名前になる
    • 未指定の場合、このメソッドは生成されない
  • 生成されたメソッドは、受け取った既存のインスタンスから各フィールドの値を取得し、新しいビルダーに設定したうえでビルダーを返すようになっている
    • 既存のインスタンスから値を取得する方法は、以下の優先順序で行われる
      1. フィールドを直接参照して取得する
      2. フィールド名と同名の Getter メソッドを使用して取得する
      3. フィールド名に対応するプロパティの Getter メソッドを使用して取得する

Staged Builder と共存できない

この toBuilder と後述する Staged Builder は共存できない。
両方を同時に設定していると、 Staged Builder の方が動作しなくなる。

具体的には、 STAGED を指定してもビルダーのプロパティを設定するメソッドの戻り値がインタフェースにならず、ビルダークラスのままになる。
つまり、次に呼び出せるメソッドが限定されなくなるので、 Staged Builder としての振る舞いが失われた状態になる。

STAGEDを指定して出力されたビルダーの一部
  // 戻り値の型がインタフェースではなくビルダーのままなので、
  // 次に呼び出すメソッドは自由になる
  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

特にドキュメントでは触れられていないので、バグなのか仕様なのかは分からない。
(STAGED の挙動を有効にすると、「他のインスタンスを元にして一部だけ変更する」という toBuilder の目的が達成できないので仕様な気はする)

Staged Builder

Hoge.java
package sandbox.jilt;

import org.jilt.Builder;
import org.jilt.BuilderStyle;

@Builder(style = BuilderStyle.STAGED)
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilders.java
package sandbox.jilt;

import java.lang.String;
import javax.annotation.processing.Generated;

@Generated("Jilt-1.6")
public interface HogeBuilders {
  interface Text {
    Number text(final String text);
  }

  interface Number {
    Optionals number(final int number);
  }

  interface Optionals {
    Hoge build();
  }
}
HogeBuilder.java
package sandbox.jilt;

import java.lang.String;
import javax.annotation.processing.Generated;

@Generated("Jilt-1.6")
public class HogeBuilder implements HogeBuilders.Text,
                                    HogeBuilders.Number,
                                    HogeBuilders.Optionals {
  private String text;

  private int number;

  private HogeBuilder() {
  }

  public static HogeBuilders.Text hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • @BuilderstyleBuilderStyle.STAGED を設定することで、 Staged Builder を生成できる
  • Staged Builder は、各プロパティを初期化するためのメソッドを呼び出せる順序が限定されたビルダーとなっている
    • 例えば上記例の場合、 text > number の順でしかプロパティの設定ができないようになっている
    • また、 textnumber を設定せずに build を呼び出すこともできない
以下は全てコンパイルエラーになる
HogeBuilder.hoge()
           .number(8) // text の前に number を設定することはできない
           .text("Hello Jilt!!")
           .build();
           
HogeBuilder.hoge()
           .text("Hello Jilt!!")
           .build(); // number の設定を省略できない
  • この仕組みは、ビルダーの各メソッドが次に呼び出すべきメソッドだけを定義したインタフェースを返すことで実現されている
  • 起点となる HogeBuilder.hoge メソッドは、 HogeBuilders.Text を返す形になっている
  • この HogeBuilders.Texttext(String text) という text プロパティを設定するメソッドだけが定義されている
  • そして、戻り値の型が HogeBuilders.Number になっている
  • HogeBuilders.Number には、 number を設定するための number(int number) メソッドだけが定義されている
  • これにより、 text プロパティを設定した後は number の設定だけしかできないようになっている
  • ビルダーのメソッドが返しているインスタンスは、いずれも同じ HogeBuilder のインスタンスだが、インタフェースによって呼び出せるメソッドを絞り込むことによって上手い具合に制御されている

任意プロパティを定義する

Hoge.java
@Builder(style = BuilderStyle.STAGED)
public class Hoge {
    private final String text;
    @Opt
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilders.java
public interface HogeBuilders {
  interface Text {
    Optionals text(final String text);
  }

  interface Optionals {
    Optionals number(@Opt final int number);

    Hoge build();
  }
}
HogeBuilder.java
public class HogeBuilder implements HogeBuilders.Text, HogeBuilders.Optionals {
  private String text;

  private int number;

  private HogeBuilder() {
  }

  public static HogeBuilders.Text hoge() {
    return new HogeBuilder();
  }

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(@Opt final int number) {
    this.number = number;
    return this;
  }

  public Hoge build() {
    return new Hoge(text, number);
  }
}
  • @Opt アノテーションを付けると、そのプロパティは任意(オプショナル)になる
  • HogeBuilders.Number インタフェースが消えて、 HogeBuilders.Optionalsnumber メソッドが追加されている
  • これにより、 number の設定は省略して書けるようになる
    • 未設定のプロパティは、各型の初期値が設定される
  • 任意プロパティは、必須プロパティを設定し終えた後で build メソッドを呼び出す前に設定できる
// number メソッドの呼び出しを省略できる
HogeBuilder.hoge().text("a").build();

// number は、必須の text を指定した後に設定できる
HogeBuilder.hoge().text("a").number(1).build();
  • @Builder をコンストラクタや static メソッドに設定している場合、 @Opt はメソッドパラメータに設定する
public class Hoge {
    private final String text;

    private final int number;

    @Builder(style = BuilderStyle.STAGED)
    public Hoge(String text, @Opt int number) {
        this.text = text;
        this.number = number;
    }
}

Nullable が設定されているプロパティは任意扱いになる

Nullable.java
package sandbox.jilt;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface Nullable {
}
  • 自前の Nullable アノテーション
Hoge.java
package sandbox.jilt;

import org.jilt.Builder;
import org.jilt.BuilderStyle;

@Builder(style = BuilderStyle.STAGED)
public class Hoge {
    private final String text;
    @Nullable
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
  • number に自作の Nullable を設定する
HogeBuilders.java
public interface HogeBuilders {
  interface Text {
    Optionals text(final String text);
  }

  interface Optionals {
    Optionals number(@Nullable final int number);

    Hoge build();
  }
}
  • numberOptionals に出力されている
  • Nullable という名前のアノテーションがつけられている場合、そのプロパティは自動的に任意扱いになる
    • 名前だけで判定しているようなので、 JSR305 の javax.annotation.Nullable でも JetBrains annotation の org.jetbrains.annotations.Nullable でも適用される

順序を維持する

Hoge.java
package sandbox.jilt;

import org.jilt.Builder;
import org.jilt.BuilderStyle;
import org.jilt.Opt;

@Builder(style = BuilderStyle.STAGED_PRESERVING_ORDER)
public class Hoge {
    @Opt
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilders.java
public interface HogeBuilders {
  interface Text {
    Number text(@Opt final String text);

    Build number(final int number);
  }

  interface Number {
    Build number(final int number);
  }

  interface Build {
    Hoge build();
  }
}
  • @BuilderstyleBuilderStyle.STAGED_PRESERVING_ORDER を設定すると、プロパティを設定する順序をフィールドの順番のまま維持できるようになる
  • BuilderStyle.STAGED の場合、任意プロパティは必須プロパティを設定した後にしか設定できなかった
  • しかし、 BuilderStyle.STAGED_PRESERVING_ORDER を設定した場合、任意プロパティも含めて設定する順序はフィールドの宣言順序と同じになる
    • 上記例では text を任意にしている
    • STAGED の場合、任意プロパティである textnumber を設定した後でないと設定できなかった
    • STAGED_PRESERVING_ORDER の場合、 number の前に text を設定できる
      • HogeBuilders.Text には text(String text) だけでなく number(int number) も定義されているので、 text を設定せずすぐに number を設定できるようになっている
STAGEDの場合
HogeBuilder.hoge()
    .number(1)
    .text("text") // text は任意なので必須プロパティの number より後にしか設定できない
    .build();
STAGED_PRESERVING_ORDERの場合
HogeBuilder.hoge()
    .text("text") // text は任意だが、フィールド宣言と同じ順序で設定できる
    .number(1)
    .build();
    
HogeBuilder.hoge()
    .number(1) // text を省略することも可能
    .build();
  • これには以下のような効果がある
    • 必須プロパティを後から任意プロパティへと変更したとき、実装の順序を変更せずに済む
    • 並びがフィールドと一致するので、目的のプロパティを見つけやすくなる

インタフェースの調整

名前を変更する

Hoge.java
package sandbox.jilt;

import org.jilt.Builder;
import org.jilt.BuilderInterfaces;
import org.jilt.BuilderStyle;

@Builder(style = BuilderStyle.STAGED)
@BuilderInterfaces(outerName = "HogeInterfaces")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeInterfaces.java
package sandbox.jilt;

import java.lang.String;
import javax.annotation.processing.Generated;

@Generated("Jilt-1.6")
public interface HogeInterfaces {
  interface Text {
    Number text(final String text);
  }

  interface Number {
    Optionals number(final int number);
  }

  interface Optionals {
    Hoge build();
  }
}
  • @BuilderInterfaces アノテーションをつけて outerName を設定することで、自動生成されるインタフェースの名前を変更できる
  • 未指定の場合のデフォルトは、ビルダークラスの名前の後ろに s がついたものになる

出力先のパッケージを変更する

Hoge.java
@Builder(style = BuilderStyle.STAGED)
@BuilderInterfaces(packageName = "sandbox.jilt.sub")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilders.java
package sandbox.jilt.sub;

import java.lang.String;
import javax.annotation.processing.Generated;
import sandbox.jilt.Hoge;

@Generated("Jilt-1.6")
public interface HogeBuilders {
  interface Text {
    Number text(final String text);
  }

  interface Number {
    Optionals number(final int number);
  }

  interface Optionals {
    Hoge build();
  }
}
  • @BuilderInterfaces アノテーションをつけて packageName を設定することで、インタフェースの出力先パッケージを変更できる
  • 未指定の場合のデフォルトは、ビルダーと同じパッケージになる

各プロパティを設定するインタフェースの名前を指定する

Hoge.java
@Builder(style = BuilderStyle.STAGED)
@BuilderInterfaces(innerNames = "*Setter")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilders.java
public interface HogeBuilders {
  interface TextSetter {
    NumberSetter text(final String text);
  }

  interface NumberSetter {
    OptionalsSetter number(final int number);
  }

  interface OptionalsSetter {
    Hoge build();
  }
}
  • @BuilderInterfaces アノテーションをつけて innerNames を設定することで、各プロパティを設定するインタフェースの名前を指定できる
    • * は、各プロパティ名(先頭大文字)に置換される
  • 未指定の場合のデフォルトは、プロパティ名の先頭を大文字にしたものになる(* を指定した場合と同じ)

任意プロパティを設定するインタフェースの名前を変更する

Hoge.java
@Builder(style = BuilderStyle.STAGED)
@BuilderInterfaces(lastInnerName = "NonRequires")
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
HogeBuilders.java
public interface HogeBuilders {
  interface Text {
    Number text(final String text);
  }

  interface Number {
    NonRequires number(final int number);
  }

  interface NonRequires {
    Hoge build();
  }
}
  • @BuilderInterfaces アノテーションをつけて lastInnerName を設定することで、最後に任意プロパティを指定するインタフェースの名前を変更できる
  • 未指定の場合のデフォルトは、 STAGED の場合は OptionalsSTAGED_PRESERVING_ORDER の場合は Build

メタアノテーション

MyBuilder.java
package sandbox.jilt;

import org.jilt.Builder;
import org.jilt.BuilderInterfaces;
import org.jilt.BuilderStyle;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Builder(style = BuilderStyle.STAGED)
@BuilderInterfaces(innerNames = "*Setter")
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface MyBuilder {
}
Hoge.java
@MyBuilder
public class Hoge {
    private final String text;
    private final int number;

    public Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }
}
  • @Builder および @BuilderInterfaces を自作のアノテーションにつけて設定をまとめることができる
  • 同じ設定を複数個所で使いまわしたい場合に便利

コンストラクタを private にする

Hoge.java
public class Hoge {
    private final String text;
    private final int number;

    @Builder
    private Hoge(String text, int number) {
        this.text = text;
        this.number = number;
    }

    public static class InnerBuilder extends HogeBuilder {
        @Override
        public Hoge build() {
            return new Hoge(text, number);
        }
    }

    public static InnerBuilder builder() {
        return new InnerBuilder();
    }
}
HogeBuilder.java
abstract class HogeBuilder {
  protected String text;

  protected int number;

  public HogeBuilder text(final String text) {
    this.text = text;
    return this;
  }

  public HogeBuilder number(final int number) {
    this.number = number;
    return this;
  }

  public abstract Hoge build();
}
実装例
final Hoge hoge = Hoge.builder().text("a").number(1).build();
  • コンストラクタを private にして、インスタンス生成をビルダー経由に限定することができる
  • @Builder を設定したコンストラクタまたは static メソッドの可視性が private の場合、自動生成されるビルダーが以下のように変化する
    • ビルダーが abstract (抽象型)になる
    • ビルダーに設定された各プロパティが protected なフィールドに格納され、サブクラスから参照できるようになる
    • build メソッドが抽象メソッドになる
  • 生成対象のクラスで以下のような実装を追加することで、ビルダー経由でインスタンスを生成できるようになる
    • 生成された抽象ビルダークラスを継承した具象ビルダークラスを定義する(実装例の InnerBuilder)
    • 具象ビルダークラスを返す、ビルダーのファクトリメソッドを定義する(実装例の builder メソッド)

参考

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3