リテラル文字列のインスタンスと参照について

会社の大先輩(っつーか俺のJavaの先生の先生)からStringについての質問を受けた。質問の内容は昔読んだ本に書いてあった内容だったので、答えた後に情報元の本を持って行った。
ついでだし、結構ひっかかりやすい所なので一応書いておこう。

String型をイコール(==)比較するとどうなるの?

簡単に言うと、Javaの基本に則って「その変数の参照先が同じならtrue、違うならfalseである」という答えになる。
Javaの「==」を使用した比較は、基本型の場合はその数値を比較し、オブジェクト型の場合はその変数への参照アドレスを比較するからだ。

どんな時に参照先が同じで、どんな時に違うの?

ここが他のクラスと違う所。言葉だけで説明するのは難しいので、サンプルで説明しよう。このようなプログラムを作成すると、どうなるのか。

String a = "A";
String b = "A";
a = b;
if (a == b) {
	System.out.println("true");
} else {
	System.out.println("false");
}

これはaもbも、同じインスタンス(aの参照先として宣言されているString)を参照している為、結果は「true」となる。
これは業務でJavaをしている人なら判る内容だろう。

では、これならどうか。

String a = "A";
String b = "A";
if (a == b) {
	System.out.println("true");
} else {
	System.out.println("false");
}

1つ目との違いは、変数aにbの参照を代入するのをやめただけだ。こうすると一見、変数aとbが別々の「"A"」を参照しているように見える。
が、この結果は「true」と表示される。
別々に「"A"」と宣言しているはずなのに、結果として参照しているStringインスタンスは同じになっているのである。

何故そうなるのか

これは、Javaの言語規定の一文を読めば理由がわかる。

文字列リテラルは,クラスStringのインスタンスへの参照とする。 オブジェクトString は,一定の値をもつ。 文字列リテラル,又はもっと一般的には,定数式の値とする文字列は,メソッド String.internを使って,一意なインスタンスを共有するために収容される。

ごめん、これじゃわからんわ orz
判りやすく説明すると、「同じリテラル文字列で宣言されているStringは同じインスタンスを参照しますよ」と言うことだ。
だから上記サンプルのaとbは同じインスタンスを参照し、「==」の比較で「true」を返すことになる。

注意とか

この法則は「リテラル文字列」にのみ適用されるので、String#substringで動的に生成した文字列や、new String("...")で生成した文字列は含まない。これらは別々のインスタンスが生成される。具体的に言うと、「"A"」を「new String("A")」にすると結果は「false」になる。
詳しい説明とサンプルはJava言語規則にあるので、気になる人は読んでみたら良いと思う。

最後に

こんな知識はStringを「==」で比較しなければ不必要なんだけど、本番で動いてるソースがこうなってるとかという有り得ん事態に遭遇した時に役立つかも。