18
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

java.lang.Stringクラスに学ぶ不変オブジェクトという考え方

Last updated at Posted at 2016-09-28

突然ですが問題です。

以下のプログラムを実行すると出力されるのは true でしょうか false でしょうか。

StringSample.java
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 です。

StringSample.java
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クラスが TRUEFALSE というBoolean型の定数を保持し、Boolean.valueOf()メソッドでは戻り値としてそのどちらかを返す(新しくnew Boolean()しない)実装になっていたり、Java8では時間を扱う不変なクラスがいくつも追加されたりと、至るところに不変なインスタンスを使い回す考え方が見て取れます。

不変オブジェクトのメリット

では、オブジェクトを不変にすることによって、どのようなメリットがあるのでしょうか。
一番のメリットは、先ほどことそのままで「インスタンスを使い回せる」ということです。

Javaでは、オブジェクトを new するたびに、新しいインスタンスが使用するメモリの確保が行われます。また、 new しすぎてメモリが足りなくなってきたらガベージコレクションが行われます。つまり、new すればするほど、プログラムの実行速度や効率が落ちてしまうのです。

そのため、特に頻繁に使われる基本的なクラスでは不変オブジェクトを活用し、なるべく new しなくても安全なプログラムが書けるような工夫がされている、というわけです。

また、オブジェクトが不変であることは並行処理をするプログラムでも有効です。

同じインスタンスを複数のスレッドが参照している場合、そのオブジェクトが書き換え可能だとどちらのスレッドがどの順番でインスタンスを書き換えるか、ということがプログラムの実行ごとに変わってしまいます。そうなると、プログラマは参照しているインスタンスにどのような変化が発生するかを分からないまま実装しなければならず、バグを生む原因にもなってしまいます。

オブジェクトが不変であることが保証されていることで、スレッドがいくつあってどのような処理をしているのかに関わらず、常に同じようにそのインスタンスを扱うことができ、想定外の不具合の発生を防げるのです。

参考

(2016.09.29追記)
自作クラスを不変にする工夫についてまとめてみました。
【Java】不変オブジェクトの作り方 参照の隠蔽と防衛的コピー
併せて読んでみてください。

18
21
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?