突然ですが問題です。
以下のプログラムを実行すると出力されるのは true
でしょうか false
でしょうか。
public class StringSample {
public static void main(String args[]) {
String hoge = "foo";
String fuga = "foo";
System.out.println(hoge == fuga);
}
}
答えは true
です。僕はずっと false
だと思っていました。
不変なオブジェクト
Javaにおいて、「文字列の比較は ==
ではなく equals()
を使え!」と教わるのは誰もが通る道だと思います。参照型であるオブジェクト同士の ==
は参照値の比較であって中身の比較ではないためです。
また、"foo"
と書くと、内部的には new String("foo")
が呼び出されるとも学んでいたため、別の場所で "foo"
と宣言して生成したオブジェクトは別の参照値を持つものと思っていました。
が、Stringクラスにはもう一つ 「同じ文字列リテラルを宣言した場合、すでにある同じ文字列を保持するStringオブジェクトへの参照値を使い回す」 という仕様があるようです。参照値を使い回す(つまり同じインスタンスを参照する)、と聞くと「どちらかの参照(変数)からインスタンスの中身を書き換えたらもう一方にも影響が出てしまうのではないか」と考えてしまいますが、Stringクラスは「不変であること」が保証される実装になっているため、そもそもインスタンスが保持している値を「書き換える」ということができないようです。
参考
文字列は定数です。この値を作成したあとに変更はできません。文字列バッファは可変文字列をサポートします。文字列オブジェクトは不変であるため、共用することができます。
なお、文字列リテラルの場合はインスタンスを使い回す、というだけなので、次の例は false
です。
public class StringSample {
public static void main(String args[]) {
String hoge = new String("foo"); // "foo" を new String()で生成
String fuga = new String("foo");
System.out.println(hoge == fuga);
}
}
この仕組みによって、同じインスタンスを使いまわしても、そのインスタンスが保持する値が意図せず変更されていた、ということが発生しないようになっているわけです。
Javaでは他にも、 Booleanクラスが TRUE
と FALSE
というBoolean型の定数を保持し、Boolean.valueOf()メソッドでは戻り値としてそのどちらかを返す(新しくnew Boolean()しない)実装になっていたり、Java8では時間を扱う不変なクラスがいくつも追加されたりと、至るところに不変なインスタンスを使い回す考え方が見て取れます。
不変オブジェクトのメリット
では、オブジェクトを不変にすることによって、どのようなメリットがあるのでしょうか。
一番のメリットは、先ほどことそのままで「インスタンスを使い回せる」ということです。
Javaでは、オブジェクトを new
するたびに、新しいインスタンスが使用するメモリの確保が行われます。また、 new
しすぎてメモリが足りなくなってきたらガベージコレクションが行われます。つまり、new
すればするほど、プログラムの実行速度や効率が落ちてしまうのです。
そのため、特に頻繁に使われる基本的なクラスでは不変オブジェクトを活用し、なるべく new
しなくても安全なプログラムが書けるような工夫がされている、というわけです。
また、オブジェクトが不変であることは並行処理をするプログラムでも有効です。
同じインスタンスを複数のスレッドが参照している場合、そのオブジェクトが書き換え可能だとどちらのスレッドがどの順番でインスタンスを書き換えるか、ということがプログラムの実行ごとに変わってしまいます。そうなると、プログラマは参照しているインスタンスにどのような変化が発生するかを分からないまま実装しなければならず、バグを生む原因にもなってしまいます。
オブジェクトが不変であることが保証されていることで、スレッドがいくつあってどのような処理をしているのかに関わらず、常に同じようにそのインスタンスを扱うことができ、想定外の不具合の発生を防げるのです。
参考
- String型から不変クラスを学ぶ【Java】
-
Effective Java
- 項目5「不必要なオブジェクトの生成を避ける」に不変オブジェクトを作るメリットが詳しく書かれています。
-
java.lang.String OpenJDK8
- Stringクラスの実装はこちらから参照できます。
(2016.09.29追記)
自作クラスを不変にする工夫についてまとめてみました。
【Java】不変オブジェクトの作り方 参照の隠蔽と防衛的コピー
併せて読んでみてください。