mutable(可変な)オブジェクトとimmutable(不変な)オブジェクト

Java Silver SE11の学習をしていて、「Stringオブジェクトは不変なオブジェクトである」の意味がよくわからなかったので、覚書ついでに。

# mutableとimmutableの違い
そもそもmutable(可変)とimmutable(不変)の違いとはなんぞやから。
簡単に言うと、一度セットした値を**後から変更できるオブジェクト**がmutableオブジェクト。
一度セットした値を**後から変更できないオブジェクト**がimmutableオブジェクト。そのままですね。

immutableオブジェクトの定義方法は下記らしいです。

- すべてのフィールドをprivateで修飾する
- オブジェクト内部の状態を変更可能なメソッド(setterなど)を提供しない
- クラスをfinalで宣言し、メソッドがオーバーライドされないことを保証する(サブクラスから変更させない)
- 内部に可変オブジェクトを保持している場合、そのオブジェクトを外部に提供(getterなど)しない

次項より、immutableなクラス:Hogeを作成することでそれぞれの詳細を確認します。
(その他のクラスも記載しますが、これらはimmutableでないものとします)

#### すべてのフィールドをprivateで修飾する
これはまぁ、わかりやすいですよね。publicで修飾してたら変え放題ですものね。

```java:Test.java
public class Test {
    public static void main(String args[]){
        Hoge hoge = new Hoge("ダイゴウジ", "ガイ" ,18);
    }
}

class Hoge{
    private String name;
    private int age;
    private Foo foo;

    Hoge(){}
    Hoge(String lastName, String firstName, int age){
        foo = new Foo(lastName, firstName);
        this.name = lastName + firstName;
        this.age = age;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public Foo getFoo() { return foo; }
    public void setFoo(Foo foo) { this.foo = foo; }

    public void dummy(){}
}

class Foo{
    private String lastName;
    private String firstName;
    Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; }

    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public String getFirstName() { return firstName; }
}
```
クラスHogeのフィールド、name、age、fooはアクセス修飾子がprivateになっています。はい。

#### オブジェクト内部の状態を変更可能なメソッド(setterなど)を提供しない
これもわかりやすいですね。せっかくprivateで修飾しても、メソッド経由で変更可能だったら何も意味ないです。

```java:Test.java
public class Test {
    public static void main(String args[]){
        Hoge hoge = new Hoge("ダイゴウジ", "ガイ" ,18);
    }
}

class Hoge{
    private String name;
    private int age;
    private Foo foo;

    Hoge(){}
    Hoge(String lastName, String firstName, int age){
        foo = new Foo(lastName, firstName);
        this.name = lastName + firstName;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public Foo getFoo() { return foo; }

    public void dummy(){}
}

class Foo{
    private String lastName;
    private String firstName;
    Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; }

    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public String getFirstName() { return firstName; }
}
```
メソッド経由での変更ができなくなりました。

#### クラスをfinalで宣言し、メソッドがオーバーライドされないことを保証する(サブクラスから変更させない)
クラス:Hogeを継承したHogeSubを作ってみます。

```java:Test.java
public class Test {
    public static void main(String args[]){
        HogeSub hogesub = new HogeSub("ダイゴウジ", "ガイ", 18);
        System.out.println("lastName:" + hogesub.getFoo().getLastName() + " firstName:" + hogesub.getFoo().getFirstName());
        System.out.println("-----------------");
        hogesub.setLastName("山田");
        hogesub.setFirstName("二郎");
        hogesub.dummy();
        System.out.println("lastName:" + hogesub.getFoo().getLastName() + " firstName:" + hogesub.getFoo().getFirstName());
    }
}

class Hoge{
    private String name;
    private int age;
    private Foo foo;

    Hoge(){}
    Hoge(String lastName, String firstName, int age){
        foo = new Foo(lastName, firstName);
        this.name = lastName + firstName;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public Foo getFoo() { return foo; }
    public void dummy(){}
}

class HogeSub extends Hoge {
    private String lastName;
    private String firstName;

    HogeSub(String lastName, String firstName, int age) {
        super(lastName, firstName, age);
        this.lastName = lastName;
        this.firstName = firstName;
    }

    @Override
    public void dummy() {
        super.getFoo().setFirstName(this.firstName);
        super.getFoo().setLastName(this.lastName);
    }

    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
}

class Foo{
    private String lastName;
    private String firstName;
    Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; }

    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public String getFirstName() { return firstName; }
}
```
実行結果は次の通りです。

```:実行結果
lastName:ダイゴウジ firstName:ガイ
-----------------
lastName:山田 firstName:二郎
```
HogeSubは、Hogeが持っているdummyメソッドをオーバーライドし、fooのフィールドを変更しています。
こういった実装を回避するため、Hogeをfinalで宣言します。

```java:Test.java
public class Test {
    public static void main(String args[]){
        Hoge hoge = new Hoge("ダイゴウジ", "ガイ", 18);
    }
}

final class Hoge{
    private String name;
    private int age;
    private Foo foo;

    Hoge(){}
    Hoge(String lastName, String firstName, int age){
        foo = new Foo(lastName, firstName);
        this.name = lastName + firstName;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public Foo getFoo() { return foo; }
    public void dummy(){}
}

class Foo{
    private String lastName;
    private String firstName;
    Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; }

    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public String getFirstName() { return firstName; }
}
```
オーバーライドによるフィールドの変更ができなくなりました。

#### 内部に可変オブジェクトを保持している場合、そのオブジェクトを外部に提供(getterなど)しない
前段は、フィールド:fooのgetterを経由し、オブジェクトの値を変更していました。
`Hoge.getFoo().setXXXName(”xxx”);`
このような操作をされてしまうと、不変とは言えませんね。getterも消します。

```java:Test.java
public class Test {
    public static void main(String args[]){
        Hoge hoge = new Hoge("ダイゴウジ", "ガイ", 18);
    }
}

final class Hoge{
    private String name;
    private int age;
    private Foo foo;

    Hoge(){}
    Hoge(String lastName, String firstName, int age){
        foo = new Foo(lastName, firstName);
        this.name = lastName + firstName;
        this.age = age;
    }
    public void dummy(){}
}

class Foo{
    private String lastName;
    private String firstName;
    Foo(String lastName, String firstName){ this.lastName = lastName; this.firstName = firstName; }

    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public String getFirstName() { return firstName; }
}
```
これで、immutableなクラス:Hogeが完成しました。
完成したHogeは実質何もできませんが、そこはサンプルということでお許しください…。

# せやかてString型変数の内容変えられるやん
**※この項は推測です。誤りがあればご指摘ください。**

```java:Test2.java
public class Test2 {
    public static void main(String args[]){
        String str = "すとりんぐ１";
        System.out.println(str);
        System.out.println("-----------------");
        str = "すとりんぐ２";
        System.out.println(str);
    }
}
```
```:実行結果
すとりんぐ１
-----------------
すとりんぐ２
```
確かにstrの内容は変わっています。
注意が必要なのは、**変数strの値(参照先)が変わっているだけであって、インスタンスの値は変わっていない**という点です。

デバッガで追ってみます。以下のようにブレークポイントを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584615/b6169fa0-5d53-1153-8c65-0b41de3bf928.png)

まずは、一つ目のブレークポイント時の内容から。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584615/10a996ca-ac63-10a6-f83d-beed56861b1a.png)

次に、二つ目のブレークポイント時の内容。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584615/f1cc8477-52c4-b1da-0e8a-21a5b0454e86.png)

strの参照先が変わっていることが確認できます。

Stringクラスは特殊で、new演算子を使わずとも、代入演算子でインスタンスの生成が可能です。
(`str = "すとりんぐ２";` と `str = new String("すとりんぐ２");` はどちらも同じ処理ということ)
つまり、String型変数に代入演算子で文字列を設定するということは、都度インスタンスを生成していることと同義です。
(厳密には、本当に都度都度生成しているとは限りませんが)

使い方によっては、JVM上のメモリをどんどん食いつぶすことになりかねません。
こういった問題を解消するために、SringBuilderクラスが作られた…のだと、推測します。

# 最後に
やはりといいますか、(推測交じりですが)アウトプットすることで理解が深まると感じました。
Silver/SE11の取得を目指して引き続き頑張ります。