概要
“immutable”の説明として、「オブジェクト生成後にその内容を変えることができないオブジェクトを”イミュータブルである”」というらしい。いま一つわかり辛かったので、以下の様に考えてみた。
オブジェクトへの参照値が変わらないのにその内容が変わり得るのがミュータブル、オブジェクトの参照値が変わらなければその内容が変わり得ないのがイミュータブル、というのはどうだろうかと考えた。
これをJavaのint
型、配列、String
型で確認してみる。
プリミティブ型はイミュータブル
以下のコードで考える。
i2
はi1
をそのまま代入しているだけなので、==
比較はtrue
- その
i2
インクリメントしてもi1
は影響を受けず、i1
とi2
の==
比較はfalse
になる
- その
i3
はi1
と同じ値をリテラルで設定していて、i1
との==
比較はtrue
i4
はi1
と異なる値を代入していて、i1との==比較はfalsei5
はi4
と同じ値を計算した上で代入しており、i4 == i5
int
型などのプリミティブ型は、変数ごとにスタック上に内容が保存されるため、オブジェクトのような参照をしない。その挙動から「プリミティブ型はイミュータブルである」ともいえる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Immutable { public void intCase() { int i1 = 2; int i2 = i1; System.out.println(i1 == i2); // true i2++; System.out.println(i1); // 2 System.out.println(i1 == i2); // false int i3 = 2; System.out.println(i1 == i3); // true int i4 = 5; System.out.println(i1 == i4); // false int i5 = i1 + 3; System.out.println(i4 == i5); // true } public static void main(String[] args) { Immutable app = new Immutable(); app.intCase(); } } |
これをラッパークラスのInteger
で試しても同じ結果になる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Immutable { public void integerCase() { Integer i1 = Integer.valueOf(2); Integer i2 = i1; System.out.println(i1 == i2); // true i2++; System.out.println(i1); // 2 System.out.println(i1 == i2); // false Integer i3 = Integer.valueOf(2); System.out.println(i1 == i3); // true Integer i4 = Integer.valueOf(5); System.out.println(i1 == i4); // false Integer i5 = i1 + 3; System.out.println(i4 == i5); // true } public static void main(String[] args) { Immutable app = new Immutable(); app.integerCase(); } } |
配列はミュータブル
配列はミュータブルなオブジェクトで、参照している内容を直接変更することができる。
以下の様に、2つの変数が同じインスタンスを参照している場合、一方でその内容を変更すると他方でもそれが反映される。
1 2 3 4 5 6 7 8 9 |
int[] a1 = {1, 2, 3, 4, 5}; int[] a2 = a1; System.out.println(a1 == a2); // true System.out.println(Arrays.equals(a1, a2)); // true a2[0] = 0; System.out.println(Arrays.toString(a1)); // [0, 2, 3, 4, 5] System.out.println(Arrays.toString(a2)); // [0, 2, 3, 4, 5] |
ただし同じ内容でもリテラルで初期化した場合は別のインスタンスとして保持され、一方への変更が他方へは影響しない。
1 2 3 4 5 6 7 |
int[] a1 = {1, 2, 3, 4, 5}; int[] a3 = {1, 2, 3, 4, 5}; System.out.println(a1 == a3); // false System.out.println(Arrays.equals(a1, a3)); // true a1[0] = 0; System.out.println(Arrays.toString(a1)); // [0, 2, 3, 4, 5] System.out.println(Arrays.toString(a3)); // [1, 2, 3, 4, 5] |
Stringはイミュータブル
String
型はイミュータブルなオブジェクトで、詳細はこちらにまとめた。
String
型の文字列インスタンスはヒープ上に保存され参照される。リテラルの内容が同じ場合は1つのインスタンスを共有してこれを参照する。
参照されている実体の内容が直接操作され変更されることはなく、変数同士で参照値を代入した場合に一方への変更が他方に影響することはない。