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 の設定
- 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"
}
-
compileOnly
とannotationProcessor
に cc.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);
}
}
-
@Builder
のpackageName
でビルダーの出力先パッケージを指定できる - 未指定の場合のデフォルトは、生成対象のクラスと同じパッケージになる
セッターメソッドのプレフィックスを指定する
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
を設定すると、既存のインスタンスを元に新しいビルダーを生成するメソッドが追加される- ここに指定した値が、そのメソッドの名前になる
- 未指定の場合、このメソッドは生成されない
- 生成されたメソッドは、受け取った既存のインスタンスから各フィールドの値を取得し、新しいビルダーに設定したうえでビルダーを返すようになっている
- 既存のインスタンスから値を取得する方法は、以下の優先順序で行われる
- フィールドを直接参照して取得する
- フィールド名と同名の Getter メソッドを使用して取得する
- フィールド名に対応するプロパティの 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);
}
}
-
@Builder
のstyle
にBuilderStyle.STAGED
を設定することで、 Staged Builder を生成できる - Staged Builder は、各プロパティを初期化するためのメソッドを呼び出せる順序が限定されたビルダーとなっている
- 例えば上記例の場合、
text
>number
の順でしかプロパティの設定ができないようになっている - また、
text
やnumber
を設定せずにbuild
を呼び出すこともできない
- 例えば上記例の場合、
以下は全てコンパイルエラーになる
HogeBuilder.hoge()
.number(8) // text の前に number を設定することはできない
.text("Hello Jilt!!")
.build();
HogeBuilder.hoge()
.text("Hello Jilt!!")
.build(); // number の設定を省略できない
- この仕組みは、ビルダーの各メソッドが次に呼び出すべきメソッドだけを定義したインタフェースを返すことで実現されている
- 起点となる
HogeBuilder.hoge
メソッドは、HogeBuilders.Text
を返す形になっている - この
HogeBuilders.Text
はtext(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.Optionals
にnumber
メソッドが追加されている - これにより、
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();
}
}
-
number
がOptionals
に出力されている -
Nullable
という名前のアノテーションがつけられている場合、そのプロパティは自動的に任意扱いになる- 名前だけで判定しているようなので、 JSR305 の
javax.annotation.Nullable
でも JetBrains annotation のorg.jetbrains.annotations.Nullable
でも適用される
- 名前だけで判定しているようなので、 JSR305 の
順序を維持する
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();
}
}
-
@Builder
のstyle
にBuilderStyle.STAGED_PRESERVING_ORDER
を設定すると、プロパティを設定する順序をフィールドの順番のまま維持できるようになる -
BuilderStyle.STAGED
の場合、任意プロパティは必須プロパティを設定した後にしか設定できなかった - しかし、
BuilderStyle.STAGED_PRESERVING_ORDER
を設定した場合、任意プロパティも含めて設定する順序はフィールドの宣言順序と同じになる- 上記例では
text
を任意にしている -
STAGED
の場合、任意プロパティであるtext
はnumber
を設定した後でないと設定できなかった -
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
の場合はOptionals
、STAGED_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
メソッド)
- 生成された抽象ビルダークラスを継承した具象ビルダークラスを定義する(実装例の