String型から不変クラスを学ぶ【Java】

String型は参照型であるにも関わらず、値渡しされているかのような振る舞いをする。...ふと、今更ながら「なんで?」と思った次第。

参照型はクラス型・インターフェース型・配列型と3つに分類でき、String型はクラス型。
実際に内部ではStringクラスとして実装されているわけだが、このStringクラスは「不変クラス(イミュータブル, immutable)」として実装されている。
これが、参照型なのに値渡しされてるように見える理由だった。


Stringクラスの実装の一部(GrepCodeへ)

// コード(1)
// Stringクラスのメンバ変数
private final char value[];
private final int offset;
private final int count;

// ...略...

public String(char value[]) {
    int size = value.length;
    this.offset = 0;
    this.count = size;
    this.value = Arrays.copyOf(value, size); // <- 防衛的コピーで不変クラスを実現
}

コード(1)はStringクラスの実装の一部。これを踏まえた上でコード(2)について考えてみる。

// コード(2)
String str = "abc";
// これは次の2行と同じ
// char data[] = {'a', 'b', 'c'};
// String str = new String(data); // <- コード(1)のメソッドが使用される

コード(1)を見ると、char型の配列を渡し、それをコピーして新たな配列を生成していることで、他のオブジェクトからの変更可能性を排除していた。
このように引数として受け取ったオブジェクトから値を取り出して、それをもとに新しいオブジェクトを生成することを「防衛的コピー」という。

Stringクラスの実装には、随所にこの防衛的コピーが施されている。

防衛的コピーは、特に、参照型のフィールドや参照型引数のメソッドを持つ不変クラスを作る上で重要な手法なので、しっかりと覚えておく。