Javaの不変クラス(Immutableクラス)の作り方を完全ガイド!初心者でもわかる設計手法
生徒
「先生、Javaで一度作ったオブジェクトの中身を変更できないようにする方法はありますか?」
先生
「はい、Javaでは不変クラス(Immutableクラス)を作成することで、オブジェクトの状態を変更できないようにできます。」
生徒
「具体的にはどうやって作るんですか?」
先生
「それでは、基本的な作り方を見ていきましょう!」
1. 不変クラス(Immutableクラス)とは?
Javaにおける不変クラス(Immutableクラス)とは、一度インスタンスを生成すると、その状態(フィールドの値)を変更できないクラスのことを指します。代表的な例として、Stringクラスがあります。Stringオブジェクトは生成後にその内容を変更することができません。
不変クラスを使用することで、以下のようなメリットがあります:
- スレッドセーフ:状態が変わらないため、複数のスレッドから同時にアクセスしても安全です。
- 予測可能性:オブジェクトの状態が変わらないため、バグの原因となる予期しない変更を防げます。
- セキュリティ:外部からの不正な変更を防ぐことができます。
2. 不変クラスを作成するための基本ルール
Javaで不変クラスを作成するためには、以下のルールを守る必要があります:
- クラスを
finalとして宣言する:クラスの継承を禁止し、サブクラスによる状態変更を防ぎます。 - すべてのフィールドを
private finalとして宣言する:外部からの直接アクセスと再代入を防ぎます。 - セッターメソッドを提供しない:フィールドの値を変更する手段を与えません。
- コンストラクタで全てのフィールドを初期化する:オブジェクト生成時に完全な状態を設定します。
- ミュータブルなオブジェクトをフィールドに持つ場合、防御的コピーを行う:外部からの変更を防ぐために、コピーを作成して保持します。
3. 不変クラスの基本的な実装例
以下は、Personクラスを不変クラスとして実装した例です。
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
このクラスでは、nameとageのフィールドがprivate finalとして宣言されており、セッターメソッドが存在しないため、オブジェクトの状態を変更することができません。
4. ミュータブルなオブジェクトを含む場合の対処法
不変クラスのフィールドにDateやListなどのミュータブルなオブジェクトを含む場合、外部からの変更を防ぐために防御的コピー(ディフェンシブコピー)を行う必要があります。
以下は、Dateオブジェクトを含む不変クラスの例です。
import java.util.Date;
public final class Event {
private final String title;
private final Date date;
public Event(String title, Date date) {
this.title = title;
this.date = new Date(date.getTime()); // 防御的コピー
}
public String getTitle() {
return title;
}
public Date getDate() {
return new Date(date.getTime()); // 防御的コピー
}
}
このように、コンストラクタとゲッターメソッドで防御的コピーを行うことで、外部からのDateオブジェクトの変更がEventオブジェクトに影響を与えることを防ぎます。
5. コレクションを含む不変クラスの実装例
コレクション(ListやMapなど)をフィールドに持つ場合も、防御的コピーを行う必要があります。以下は、StudentクラスがList<String>のcoursesフィールドを持つ例です。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class Student {
private final String name;
private final List<String> courses;
public Student(String name, List<String> courses) {
this.name = name;
this.courses = new ArrayList<>(courses); // 防御的コピー
}
public String getName() {
return name;
}
public List<String> getCourses() {
return Collections.unmodifiableList(courses); // 不変のリストを返す
}
}
この実装では、コンストラクタでcoursesリストのコピーを作成し、ゲッターメソッドでは不変のリストを返すことで、外部からの変更を防いでいます。
6. Java 14以降のrecordを使った不変クラスの作成
Java 14以降では、recordキーワードを使用することで、簡潔に不変クラスを作成できます。recordは以下の特徴を持ちます:
finalクラスとして自動的に定義される- すべてのフィールドが
private finalとして定義される - コンストラクタ、ゲッター、
equals()、hashCode()、toString()メソッドが自動生成される
以下は、Personクラスをrecordで定義した例です。
public record Person(String name, int age) {
}
このように、recordを使用することで、ボイラープレートコードを減らし、簡潔に不変クラスを定義できます。
7. 不変クラスの活用例とメリット
不変クラスは、以下のような場面で活用されます:
- マルチスレッド環境:スレッドセーフなオブジェクトとして使用できます。
- キャッシュのキー:状態が変わらないため、ハッシュコードが一定であり、キャッシュのキーとして適しています。
- セキュリティ:外部からの不正な変更を防ぐことができます。
また、不変クラスを使用することで、コードの可読性と保守性が向上し、バグの発生を抑えることができます。