TOP 読み方 Tips イディオム UML JDE ツール APPLET 私情 情報源

イミュータブル

イミュータブル(Immutable、不変)というオブジェクトの性質はオブジェクト 指向プログラミングにおいて非常に大きな意味合いを持っています。
複製可能である性質、整列化可能である性質はjava.lang.Cloneableや java.io.Serializableというインタフェースによって言語仕様の表層に出てい ますが、イミュータブルという性質は言語仕様では直接サポートはされない設 計上のコツといったもので、直接取り上げられる機会は少ないといえます。 しかしその重要度は決して低いものではありません。

不変という日本語訳からも分かる通り、イミュータブルはオブジェクトの生成 時に設定された属性を変更することができないオブジェクトの性質です。
一見、属性を後から変更することができない性質はデメリットであるようにし かみえません。

class Figure {
    ....

    public void move(double x, double y) {
        region_.x = x;
        region_.y = y;
    }

    Region region_;
}

としたいところを

class Figure {
    ....

    public void move(double x, double y) {
        region_ = new Region(x + region_.getX(), y + region_.getY());
    }

    Region region_;
}

としなければならないのはあまりにもムダと思えるものです。

しかしイミュータブルにはこの欠点を補ってあまりあるメリットがあるのです。
オブジェクトをイミュータブルとすることのメリットは以下の2つ。

つまりオブジェクト指向プログラミングで取り扱わなければならない2つの大 きな問題をまったく気にする必要がなくなるのです。 プログラミングの負荷の軽減に大きく貢献します。
またこれはプログラミング上の都合だけでなく性能にも影響します。 オブジェクトの所属を気にする必要がある場合にはオブジェクトの複製を生成 する可能性が高くなり、その処理の分、性能にインパクトがあります。もちろ んメモリ資源も余分に必要となりガベージコレクションの発生、ワーキングセッ トの増大といった2次的な要因も無視できません。
さらにオブジェクトの相互排除ではsynchronizedによるモニタ機能を使用する 必要がありますが、この機能を使用することは相応の性能劣化が発生します。
イミュータブルにできるクラスはできるだけイミュータブルとして設計する。 これはJavaのプログラミングテクニックとして非常に重要であるといえます。

Javaではjava.lang.Stringがイミュータブルの典型例です。 java.lang.Integerやjava.lang.Floatなどのラッパクラスや、java.awt.Color といったところもイミュータブル。
イミュータブルはすでにJavaのプログラミングテクニックとして広く利用され ていることが分かります。


イミュータブルオブジェクトの実装

イミュータブルオブジェクトの例としてX/Y座標の中の範囲を表すRegionを考 えてみます。
イミュータブルオブジェクトは以下の方針で実装します。

public class Region {
    public Region(double x, double y, double width, double height) {
        x_ = x;
        y_ = y;
        width_ = width;
        height_ = height;
    }

    public final double getX() {
        return (x_);
    }

    public final double getY() {
        return (y_);
    }

    public final double getWidth() {
        return (width_);
    }

    public final double getHeight() {
        return (height_);
    }

    private double x_;
    private double y_;
    private double width_;
    private double height_;
}

イミュータブルオブジェクトの使用例として、Regionを属性値として持つオブ ジェクトFigureを考えてみます。

public class Figure {
    public Figure(Region region) {
        region_ = region;
    }
    
    public Region getRegion() {
        return (region_);
    }

    private Region region_;
}

一見ごく普通の実装に見えますね。
しかしイミュータブルでない場合と比較すると違いは明らかになります。


イミュータブルでない場合

次にイミュータブルでないバージョンは次のようになります。
setValuesというメソッドを定義したことにより、オブジェクトの値は、この オブジェクトを参照しているかもしれない複数のオブジェクトの中の一つでも その意思を持てば変更できるようになりました。
このため各メソッドでsynchronizedによる相互排除が必要になっています。
synchronizedによる相互排除は即、性能劣化の要因となります。

public class Region implements Cloneable {
    public Region(double x, double y, double width, double height) {
        x_ = x;
        y_ = y;
        width_ = width;
        height_ = height;
    }

    public final synchronized double getX() {
        return (x_);
    }

    public final synchronized double getY() {
        return (y_);
    }

    public final synchronized double getWidth() {
        return (width_);
    }

    public final synchronized double getHeight() {
        return (height_);
    }

    public final synchronized void setValues(
        double x, double y, double width, double height
    ) {
        x_ = x;
        y_ = y;
        width_ = width;
        height_ = height;
    }

    private double x_;
    private double y_;
    private double width_;
    private double height_;
}

さらに影響があるのがオブジェクトを使う時。

public class Figure {
    public Figure(Region region) {
        region_ = region.clone();
    }

    public Region getRegion() {
        return (region_.clone());
    }

    private Region region_;
}


Regionオブジェクトのクローンをしまくっています。
これは以下のような使われ方をしても誤動作しないため。 イミュータブルにしないということは、非常にコストのかかることなのです。

    Region myRegion = new Region(0, 0, 10, 10);
    Figure figure = new Figure(myRegion);
    myRegion.setValue(100, 100, 10, 10); //★もしcloneしていないとFigureのRegion値が変ってしまう
    Region anothorRegion = figure.getRegion();
    anotherRegion.setValue(10, 50, 200, 100); //★もしcloneしていないとFigureのRegion値が変ってしまう


補足

イミュータブルでないRegionオブジェクトで値の設定メソッドを属性値毎に用 意するとどうなるでしょう。

public class Region implements Cloneable {
    public Region(double x, double y, double width, double height) {
        x_ = x;
        y_ = y;
        width_ = width;
        height_ = height;
    }

    public final synchronized double getX() {
        return (x_);
    }

    public final synchronized double getY() {
        return (y_);
    }

    public final synchronized double getWidth() {
        return (width_);
    }

    public final synchronized double getHeight() {
        return (height_);
    }

    public final synchronized void setX(double x) {
        x_ = x;
    }

    public final synchronized void setY(double y) {
        y_ = y;
    }

    public final synchronized void setWidth(double width) {
        width_ = width;
    }

    public final synchronized void getHeight(double height) {
        height_ = height;
    }

    private double x_;
    private double y_;
    private double width_;
    private double height_;
}

複数の属性(この場合は4つ)を同時に変更する場合、相互排除を考慮すると以 下のようにする必要があります。
マルチスレッド環境では属性の値を変更するのもひと苦労。

    Region myRegion = new Region(0, 0, 10, 10);
    synchronized (myRegion) {
        myRegion.setX(20);
        myRegion.setY(50);
        myRegion.setWidth(100);
        myRegion.setHeight(70);
    }


関連記事
コーナー 記事 関連
Java Tips カージナルオブジェクトの設計 カージナルオブジェクトとエンティティオブジェクトはイ ミュータブルの候補
エンティティオブジェクトの設計

ホームページ 戻る メール
ASAMI Tomoharu (tasami@ibm.net)
Last modified: Wed Feb 24 09:50:42 JST 1999