「コンストラクタの引数が多すぎて、どの引数がどの項目か分からない」「オブジェクトの生成処理を分かりやすくしたい」といった悩みを抱えていませんか?本記事では、そのような課題を解決する「Builder(ビルダー)パターン」について、JavaJava [ジャバ]オブジェクト指向プログラミング言語での具体的な実装例を交えてわかりやすく解説します。
記事のポイント
- Builderパターンは、複雑なオブジェクトの生成と表現を分離するデザインパターンです。
- Javaでは特に「Effective Java」で紹介された静的メンバークラス(static member class)を用いたBuilderパターンが広く使われています。
- 引数が多いクラスでも、可読性が高く、不変オブジェクトを生成しやすくなります。
- Lombokの
@Builderアノテーションを使えば、ボイラープレートコードを省略して簡単に実装できます。
JavaにおけるBuilder(ビルダー)パターンとは?
Builder(ビルダー) パターンは、GoF(Gang of Four)によって定義された23のデザインパターンのうち、「生成に関するパターン」の1つです。
本来のGoFパターンにおけるBuilderは、複雑なオブジェクトの「生成過程」と「表現」を分離し、同じ生成過程で異なる表現のオブジェクトを作れるようにするものです。(Director、Builder、ConcreteBuilderなどの役割が登場します)
しかし、 Javaでは、GoFのBuilderパターンを簡略化した、Effective Javaで広く知られるfluent builderスタイル(静的メンバークラスを使用) も非常によく使われています。本記事では、Java開発でよく利用される「Effective Javaスタイル」のBuilderパターンを中心に解説します。
コンストラクタ引数が多い時の課題とビルダーパターンの必要性
通常のオブジェクト指向プログラミングでは、newキーワードを使ってコンストラクタからインスタンスを生成します。しかし、設定項目(フィールド)が多いクラスの場合、以下のような問題が発生します。
// 悪い例:引数が多すぎるコンストラクタUser user = new User("Tanaka", "Taro", 30, "Tokyo", "090-XXXX-XXXX", true);- 可読性の低下:どの値がどのフィールドに設定されるのか、ひと目で分かりません。
- ミスの誘発:同じ型(Stringstring [ストリング]文字列を表すデータ型など)が連続していると、引数の順番を間違えやすくなります。
- パターンの肥大化:必須項目と任意項目が混在する場合、引数の数が異なるコンストラクタを大量に用意することになります(これをテレスコーピング・コンストラクタと呼び、引数が増えるほど可読性や保守性が低下しやすくなります)。
JavaでのBuilder(ビルダー)パターンの実装例(Effective Javaスタイル)
これらの課題を解決するのが、静的メンバークラス(static member class)を使ったBuilderパターンです。
1. クラスとBuilder(ビルダー)の定義
ユーザー情報を保持するUserクラスを例に実装してみましょう。
public class User { // 必須パラメータ private final String firstName; private final String lastName;
// 任意パラメータ private final int age; private final String address; private final String phone;
// 1. privateなコンストラクタ(外部から直接newできないようにする) private User(UserBuilder builder) { this.firstName = builder.firstName; this.lastName = builder.lastName; this.age = builder.age; this.address = builder.address; this.phone = builder.phone; }
// ゲッターのみ提供(不変オブジェクトにするためセッターは設けない) public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public String getAddress() { return address; } public String getPhone() { return phone; }
// 2. 静的メンバークラスとしてBuilderを定義 public static class UserBuilder { private final String firstName; private final String lastName; private int age; private String address; private String phone;
// 必須パラメータはBuilderのコンストラクタで受け取る public UserBuilder(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
// 任意パラメータを設定するメソッド(自身のインスタンスを返すのがポイント) public UserBuilder age(int age) { this.age = age; return this; }
public UserBuilder address(String address) { this.address = address; return this; }
public UserBuilder phone(String phone) { this.phone = phone; return this; }
// 3. 最後にオブジェクトを生成するbuildメソッド public User build() { return new User(this); } }}2. クライアント側の実行例(Builderの呼び出し)
作成したBuilderを使って、Userクラスのインスタンスを生成してみます。
public class Main { public static void main(String[] args) { // メソッドチェーンを使って直感的にオブジェクトを生成できる User user = new User.UserBuilder("Taro", "Tanaka") .age(30) .address("Tokyo") .phone("090-XXXX-XXXX") .build();
System.out.println(user.getFirstName() + " " + user.getLastName()); }}JavaでBuilder(ビルダー)パターンを採用する3つのメリット
1. 可読性の向上
userBuilder.age(30).address("Tokyo") のように、どの項目に何の値を設定しているのかがメソッド名から明確になります。これにより、引数の順番を間違えるバグを防ぐことができます。
2. 必須項目と任意項目の明確な分離
この実装では、必須パラメータをBuilderのコンストラクタで受け取ることで、必須項目の指定漏れをコンパイル時に検出できます。任意項目は必要なものだけをメソッドチェーンで追加できます。
3. 不変(Immutable)オブジェクトの作成が容易
JavaBeansパターン(引数なしコンストラクタで生成し、setterで値を入れる手法)では、オブジェクトの生成途中で不完全な状態が存在し、値が後から変更されるリスクがあります。Builderパターンは不変オブジェクトの生成と相性がよく、フィールドをfinalにするなど適切に設計することで、不変オブジェクトを作成できます(ただし、Listなどの参照型フィールドを持つ場合は、フィールドをfinalにするだけでは完全な不変オブジェクトにはなりません。ビルダー内でList.copyOf()を適用するなど、防御的コピーを行う設計の工夫が必要です)。
Lombok(@Builder)を使ってJavaのビルダー実装を簡略化する
Javaの強力なライブラリである「Lombok」を使用すれば、上記のような冗長なBuilderクラスを自分で書く必要はありません。クラスに@Builderアノテーションを付与するだけで、コンパイル時に自動的にBuilderが生成されます。
import lombok.Builder;import lombok.Getter;
@Builder@Getterpublic class User { private final String firstName; private final String lastName; private final int age; private final String address; private final String phone;}Lombokを利用することで、Builderクラスを手作業で記述する手間を省くことができます。
Lombok版の注意点:必須パラメータの強制は自動では引き継がれない
先ほど手動での実装例のメリットとして「コンパイルレベルで必須項目の未設定を防げる」と解説しましたが、クラスに単に@Builderアノテーションを付与しただけの場合、すべてのフィールドが任意扱いになります。そのため、そのままでは必須項目の設定漏れをコンパイル時に防ぐことはできません。
Lombokを利用しつつ必須パラメータを強制したい場合は、必須引数を持つカスタムコンストラクタを定義し、そこに@Builderアノテーションを付与するなどの追加の工夫が必要になる点には注意してください。あるいは、フィールドに@NonNullアノテーションを付与して、設定漏れがあった場合に実行時エラー(NullPointerException)を投げるアプローチも有効です。
モダンJava(Java 21/25等)における不変オブジェクトとRecordsの台頭
2026年現在のモダンJava(Java 21/25 LTSなど)の実務では、不変オブジェクトを作成する際、Builderパターンを適用した従来のクラス設計ではなくJava Recordsの採用が主流になりつつあります。
Java 16以降で標準化されたRecordsを使えば、イミュータブルなデータクラスを極めて簡潔に定義できるため、単なるデータ保持目的であればBuilderパターンを用いなくても事足りるケースが急増しています。
さらに、Recordに対してBuilderを自動生成したい場合は、Lombok(1.18.20以降でRecord対応)の他にも、プロパティの構築順をインターフェースで厳密に強制する「Jilt(Staged Builder)」や、一部の値のみを変更した新規インスタンス生成用のWitherメソッドを自動生成する「RecordBuilder」といったモダンJava向けの特化ツールも広く利用されています。
まとめ:Java開発で覚えておきたいBuilder(ビルダー)パターン
Builderパターン(特にEffective Javaスタイル)は、パラメータが多く複雑なオブジェクトを、安全かつ可読性の高いコードで生成するための強力な手法です。
- コンストラクタの引数が増えすぎた場合
- どの値がどのプロパティか分かりにくくなった場合
- オブジェクトを不変(Immutable)に保ちたい場合
このような場面では、Builderパターンの導入を検討するとよいでしょう。また、Spring BootなどのJava開発でも、Lombokを導入して@Builderを利用するケースがありますので、あわせて覚えておきましょう。
一方で、パラメータが少ない単純なクラスにBuilderを導入すると、かえってコード量が増えて冗長になる場合があります。そのため、すべてのオブジェクト生成にBuilderを使うのではなく、コンストラクタやファクトリーメソッドなどと適切に使い分けることが重要です。
【参考情報】
以上で本記事の解説を終わります。
よいITライフを!
人気記事
- 1
- 2
- 3
- 4
- 5
思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。