前回はMapを使ってキーと値をセットで管理する方法を学びました。
今回は「イミュータブルクラス」と、Java 16から正式導入された「record」について解説します。 一度作ったら変更できないオブジェクトの設計方法を学ぶことで、バグを生みにくい安全なコードが書けるようになります。
イミュータブルクラスとは
「イミュータブル(immutable)」とは「変更不可能」という意味です。一度インスタンスを作ったら、その中身を変えられないクラスのことをイミュータブルクラスと呼びます。
String クラスはその代表例です。"hello" という文字列は変更できず、toUpperCase() などのメソッドは元の文字列を変えずに新しい文字列を返します。
イミュータブルクラスを作るには、以下のポイントを守ります。
- すべてのフィールドを
private finalにする - セッター(setXxx)を用意しない
- コンストラクタで値を設定し、ゲッター(getXxx)だけを用意する
イミュータブルにすることで、意図せず値が変更されるバグを防ぎやすくなり、マルチスレッド環境でも安全に共有できます。
サンプルコード(イミュータブルクラス)
ファイル作成
「Java14」フォルダに Point.java、ImmutableTest.java という名前でファイルを作成します

コード入力
Point.java に以下のコードを入力します。
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Point move(int dx, int dy) {
return new Point(x + dx, y + dy);
}
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}

ImmutableTest.java に以下のコードを入力します。
public class ImmutableTest {
public static void main(String[] args) {
Point p1 = new Point(3, 5);
System.out.println("p1: " + p1);
Point p2 = p1.move(2, -1);
System.out.println("p2: " + p2);
// p1は変わっていない
System.out.println("p1はそのまま: " + p1);
}
}

コンパイル
javac Point.java ImmutableTest.java

実行
java ImmutableTest
実行結果は次のようになります。
p1: Point(3, 5)
p2: Point(5, 4)
p1はそのまま: Point(3, 5)

recordの基本
イミュータブルなデータ保持クラスを毎回手書きするのは手間がかかります。private final フィールド・コンストラクタ・ゲッター・toString() など、決まり切った記述が多くなりがちです。
Java 16から正式導入された「record」を使うと、これらを自動的に生成してくれます。
public record レコード名(型 フィールド名, ...) { }
たったこれだけで、以下が自動的に用意されます。
private finalフィールド- 全フィールドを引数に取るコンストラクタ
- 各フィールドのゲッターメソッド
toString()・equals()・hashCode()
サンプルコード(record)
ファイル作成
「Java14」フォルダに Person.java、RecordTest.java という名前でファイルを作成します

コード入力
Person.java に以下のコードを入力します。
public record Person(String name, int age) { }

RecordTest.java に以下のコードを入力します。
import java.util.ArrayList;
import java.util.List;
public class RecordTest {
public static void main(String[] args) {
Person p1 = new Person("ぱんまる", 25);
Person p2 = new Person("たろう", 30);
Person p3 = new Person("ぱんまる", 25);
// 自動生成されたtoStringを使って表示
System.out.println(p1);
System.out.println(p2);
// ゲッターはフィールド名と同じメソッド名
System.out.println("名前: " + p1.name());
System.out.println("年齢: " + p1.age());
// 自動生成されたequalsで内容比較
System.out.println("p1とp3は同じ?: " + p1.equals(p3));
System.out.println("p1とp2は同じ?: " + p1.equals(p2));
// ListやMapとも組み合わせて使える
List<Person> people = new ArrayList<>();
people.add(p1);
people.add(p2);
people.forEach(p -> System.out.println(p.name() + "さん(" + p.age() + "歳)"));
}
}

コンパイル
javac Person.java RecordTest.java

実行
java RecordTest
実行結果は次のようになります。
Person[name=ぱんまる, age=25]
Person[name=たろう, age=30]
名前: ぱんまる
年齢: 25
p1とp3は同じ?: true
p1とp2は同じ?: false
ぱんまるさん(25歳)
たろうさん(30歳)

コードの解説
イミュータブルクラスの Point では、フィールドに private final をつけてセッターを用意していないため、一度作ったインスタンスの x・y を外から変えることができません。move() メソッドは自分自身を変更するのではなく、新しい Point インスタンスを作って返しています。クラス自体にも final をつけることで、継承によってイミュータブルが破られることも防いでいます。
toString() を @Override で定義することで、System.out.println(p1) のように直接表示したときに Point(3, 5) という読みやすい形式で出力されます。
recordの Person では、たった1行 public record Person(String name, int age) { } と書くだけで、コンストラクタ・ゲッター・toString()・equals()・hashCode() がすべて自動生成されます。通常のクラスのゲッターは getName() のような形ですが、recordのゲッターはフィールド名そのものの name() という形になります。
equals() が自動生成されているため、p1.equals(p3) のように内容を比較でき、name と age が同じであれば true が返ります。以前学んだArrayListやラムダ式とも自然に組み合わせて使えます。
まとめ
イミュータブルクラスは private final フィールドとセッターなしのゲッターのみで設計し、値を変えたいときは新しいインスタンスを返すことで、意図しない変更を防ぎます。recordを使うと、イミュータブルなデータ保持クラスに必要なコードを大幅に省略できます。データを入れておくだけのシンプルなクラスを作りたいときは、まずrecordの使用を検討してみましょう。イミュータブルな設計はマルチスレッド環境でも安全で、バグを防ぎやすいコードにつながります。
2026/07/04(土) ぱんまる


コメント