イミュータブルオブジェクトとは
コンピュータ
プログラミングにおいて、イミュータブル(immutable)なオブジェクトとは、
生成後にその状態を変更できないオブジェクトのことです。これは、一度作成されたら、その値や属性が変更されないことを意味します。対照的に、ミュータブル(mutable)なオブジェクトは、作成後でもその状態を変更することができます。イミュータブルオブジェクトは「不変」、ミュータブルオブジェクトは「可変」と表現されることもあります。
イミュータブルオブジェクトの特性
イミュータブルオブジェクトは、オブジェクト全体が不変である場合もあれば、一部の属性のみが不変である場合もあります。例えば、
C++の`const`データメンバは、その属性がイミュータブルであることを示します。また、内部的な実装として、コストのかかる計算結果をキャッシュ(メモ化)していても、外部から見たオブジェクトの状態が変わらないならば、そのオブジェクトはイミュータブルとみなすことができます。
イミュータブルオブジェクトの初期状態は、通常、生成時に設定されます。しかし、オブジェクトが実際に使用されるまで初期化を遅延させることも可能です。
イミュータブルオブジェクトの利点
イミュータブルオブジェクトを使用すると、以下のようなメリットが得られます。
コードの単純化: 複製や比較のための操作を省略できるため、コードがシンプルになります。
性能の改善: オブジェクトの複製が不要になるため、メモリ使用量を削減し、プログラムの実行速度を向上させることができます。
マルチスレッドプログラミングの安全性: イミュータブルオブジェクトは、複数のスレッドから同時にアクセスしても、データが変更される心配がないため、排他制御が不要になります。これにより、スレッドセーフなプログラムを容易に作成できます。
ただし、オブジェクトが変更可能なデータを多く持つ場合は、イミュータブル化が不適切な場合もあります。多くのプログラミング言語では、イミュータブルとミュータブルのどちらかを選択できるように設計されています。
イミュータブルオブジェクトの背景
多くのオブジェクト指向言語では、オブジェクトは参照を通じてやり取りされます。参照を共有することで、複数の場所から同じオブジェクトにアクセスできますが、ミュータブルなオブジェクトの場合、ある場所でオブジェクトの状態が変更されると、他の場所にも影響が及びます。これは、意図しないバグの原因となることがあります。
イミュータブルオブジェクトを使用すれば、オブジェクトの複製は参照の複製だけで済むため、メモリや実行速度の面で効率的です。また、ミュータブルなオブジェクトのように、変更による影響を考慮する必要がないため、プログラムの複雑さを軽減できます。
参照コピーと防衛的コピー
ミュータブルなオブジェクトの参照コピーは、オブジェクトの状態が変更されると、参照を共有するすべての場所に影響を及ぼします。これを防ぐために、オブジェクト全体の防衛的コピーを作成することがありますが、これはコストがかかります。Observerパターンもミュータブルなオブジェクトの変更に対処するために利用できます。
インターン
等価なオブジェクトのコピーを作成する代わりに、常に参照を複製するテクニックをインターンといいます。インターンを使用すると、オブジェクトの等価性の比較を、ポインタの比較だけで行うことができるため、プログラムの高速化が期待できます。ただし、インターンは、オブジェクトがイミュータブルである場合にのみ有効です。
イミュータブルオブジェクトの実装
イミュータブルとは、オブジェクトがメモリ上で書き込み不可能であるという意味ではありません。むしろ、イミュータブルは、プログラマが「何をすべきか」を示す概念であり、必ずしも「何ができるか」を制限するものではありません。例えば、CやC++では型システムを回避することも可能ですが、イミュータブルを尊重することが重要です。
ミュータブルとイミュータブルの利点を両立させるためのテクニックとして、コピーオンライトがあります。これは、オブジェクトを複製する代わりに、最初は参照を共有し、オブジェクトが変更される際に、初めて複製を作成する方法です。これにより、イミュータブルオブジェクトの効率を維持しつつ、ミュータブルオブジェクトのように振る舞うことができます。コピーオンライトは、仮想記憶システムなどでよく利用されています。
イミュータブルオブジェクトの例
Javaの`String`クラスは、イミュータブルオブジェクトの代表的な例です。`String`オブジェクトに対してメソッドを呼び出しても、元のオブジェクトは変更されず、新しいオブジェクトが作成されます。
java
String str = "ABC";
str.toLowerCase(); // str は変更されない
str = str.toLowerCase(); // 新しいStringオブジェクトがstrに代入される
オブジェクトがイミュータブルであるためには、フィールドがミュータブルであるかどうかとは別に、そのフィールドを書き換える方法があってはなりません。また、ミュータブルなフィールドを読み書きする方法もあってはなりません。
ミュータブルなオブジェクトの例
java
class MutableCart {
private List items;
public MutableCart(List items) {
this.items = items;
}
public List getItems() {
return this.items;
}
}
部分的にイミュータブルなオブジェクトの例
java
import java.util.Collections;
import java.util.List;
class ImmutableCart {
private final List items;
public ImmutableCart(List items) {
this.items = Collections.unmodifiableList(items);
}
public List getItems() {
return this.items;
}
}
`java.util.Collections.unmodifiableList()`は、指定されたリストの変更不可能なビューを返します。これにより、`items`フィールドを書き換えることはできませんが、リストの要素がイミュータブルである保証はありません。
C++では、`const`修飾子を使用することで、イミュータブルなオブジェクトを実装できます。以下の例では、ミュータブルなバージョンとイミュータブルなバージョンの両方の`getItems()`メソッドを提供しています。
cpp
include
include
template
class Cart {
private:
std::vector m_items;
public:
Cart(const std::vector& items) : m_items(items) {}
std::vector& getItems() {
return m_items;
}
const std::vector& getItems() const {
return m_items;
}
// または、以下の様に防御的コピーを返すことも可能
// std::vector getItems() const {
// return m_items;
// }
};
C++では、コンストラクタで渡された値をコピーするため、Javaのように参照を直接保持することによる問題を回避できます。また、`const`修飾されたオブジェクトからは、`const`修飾されたメンバーにしかアクセスできないため、破壊的な操作を防ぐことができます。
ただし、内部状態への参照を返す場合は、その参照が無効にならないように注意が必要です。安全のため、イミュータブル用の`getItems()`メソッドは、ディープコピーを返すように実装することもあります。
C++では、共有ポインタを使用することで、Javaのようにコンストラクタ呼び出し元と状態を共有する設計にすることも可能です。
イミュータブルクラスの確認
クラスがイミュータブルであるかどうかを確認するために、FindBugsなどのツールを利用することができます。これらのツールは、バグの温床となるコードを自動的に検出するのに役立ちます。
関連項目
副作用 (プログラム)
Decorator パターン
Flyweight パターン
参考資料
Pattern: Immutable Object by Nat Pryce
Java theory and practice: To mutate or not to mutate? by Brian Goetz
Java Practices: Immutable objects
Immutable Object - Portland Pattern Repository
*
Perl-Design-Patterns-Peter-Norvig/dp/0201615642'>
Perl Design Patterns