技術とか戦略とか

証券レガシーシステムを8年いじってから転職した普通の文系SEによるブログ。技術のみではなく趣味の戦略考察についても。

java:イミュータブルなクラスを作る方法

「イミュータブル」とは「不変」という意味で、オブジェクト指向の世界では「状態(クラス変数)がオブジェクト生成時から変更されないこと」を指します。
有名所では、Stringがイミュータブルなクラスとして知られています。
 
イミュータブルなクラスを自作するためには、以下の条件を満たすようにクラスを作成する必要があります。
 
①クラス変数はprivateで定義する
 クラス変数が外のクラスから直接書き換えられるのを防ぐため
 
②setterメソッドを定義しない
 クラス変数を書き換えるメソッドは定義しない
 
③クラスをfinalで定義する
 サブクラスでクラス変数を書き換えられるのを防ぐため
 
④ミュータブルなオブジェクトへの参照を含んでいない
 ミュータブルなオブジェクトを変更する処理を記述しないようにするため
 
クラスをイミュータブルにするメリットとしては、内部の状態の変化を気にする必要が無くなるために処理を追いやすくなるというのがあります。
関数型プログラミングにも通じる考え方なのですが、オブジェクト生成より後で内部の状態が書き変わることがないため、クラスのメソッドが返す値が一定となり、内部の状態の変化を気にしながらトレースする必要がなくなります。
また、
hoge2 = hoge1;
のようなオブジェクトのアドレスのコピーが使いやすくなります。
イミュータブルではないクラスでこのコピー方法を用いると、hoge2の状態の変化がhoge1にも影響してしまい(hoge1とhoge2で同じアドレスを指しているため)、思わぬ影響が出ることがあります。
しかし、イミュータブルなクラスであれば、状態を変化させようと思えば新たにオブジェクトを作り直して新たなメモリ領域を確保しなければならないので、hoge2の状態を変化させても(オブジェクトを作り直しても)hoge1へ影響するのを防ぐことができます。
イミュータブルではないクラスでも、オブジェクトの中身全体をコピーすれば思わぬ影響を防ぐことはできるのですが、オブジェクトのアドレスをコピーする場合と比べてメモリ使用量が増え処理も遅くなります。
 
以下で、イミュータブルなクラスを定義してそれを使用するサンプルコードを示します。
 
【サンプルコード】
・ImmutableCalc.java
// ③クラスをfinalで定義する
public final class ImmutableCalc {

    // ④ミュータブルなオブジェクトへの参照を含んでいない

    // ①クラス変数はprivateで定義する
    private int num = 0;

    // オブジェクト生成時のみ状態が変化する
    public ImmutableCalc(int num) {
        this.num = num;
    }

    // ②setterメソッドを定義しない
    public int singleCalc() {
        return num;
    }

    public int doubleCalc() {
        return num * 2;
    }

    public int tripleCalc() {
        return num * 3;
    }

}
 
・ImmutableTestMain.java
public class ImmutableTestMain {

    public static void main(String[] args) {

        // イミュータブルなクラスのオブジェクトを作成
        ImmutableCalc hoge1 = new ImmutableCalc(1);

        // オブジェクトのアドレスをコピー
        // オブジェクトの中身をコピーするよりメモリ使用量が減り処理も速い
        ImmutableCalc hoge2 = hoge1;

        // 値を書き換えるにはnewして新たにメモリ領域を確保するしかない
        hoge2 = new ImmutableCalc(2);

        // hoge2の変更がhoge1に影響を与えない
        // アドレスをコピーするテクニックを安心して使える
        System.out.println("hoge1:"+hoge1.singleCalc());
        System.out.println("hoge2:"+hoge2.singleCalc());

        // オブジェクト生成時にしか値が書き変わらないので処理を追いやすい
        System.out.println("hoge1_double:"+hoge1.doubleCalc());
        System.out.println("hoge1_triple:"+hoge1.tripleCalc());

    }

}
 
【実行結果】
hoge1:1
hoge2:2
hoge1_double:2
hoge1_triple:3