Javaの文字列リテラルとそのインスタンス

Javaで、同じ内容の文字列リテラルで初期化されたString型のクラスは、同じインスタンスになるようだ。 表現が下手なせいか、文章だと何を言っているかわからないけども、例えば以下のコード。

    String str1 = "Hello World!";
    String str2 = "Hello World!";
    String str3 = "Hello " + "World!";

上記では、String型の変数str1~str3が宣言されおり、初期値が文字列リテラルで指定されている。 初期値はすべて"Hello World!"だ。 このような場合、str1~str3は、同じインスタンスを指している。

以下が、その内容を確認するためのサンプルコードとなる。

サンプルコード
    String str1 = "Hello World!";
    String str2 = "Hello World!";
    String str3 = "Hello " + "World!";
    String str4 = new String("Hello World!");
    String str5 = new String(str1);

    // equalsメソッドで、クラスの中身(文字列)を比較
    System.out.println("str1.equals(str2) is " + str1.equals(str2));  // 文字列リテラル同士
    System.out.println("str1.equals(str3) is " + str1.equals(str3));  // 文字列リテラル同士
    System.out.println("str1.equals(str4) is " + str1.equals(str4));  // 文字列リテラルとnewしたもの。
    System.out.println("str1.equals(str5) is " + str1.equals(str5));  // 文字列リテラルとnewしたもの。

    // "=="でインスタンス自体を比較
    System.out.println("str1 == str2 is " + (str1==str2));  // 文字列リテラル同士
    System.out.println("str1 == str3 is " + (str1==str3));  // 文字列リテラル同士
    System.out.println("str1 == str4 is " + (str1==str4));  // 文字列リテラルとnewしたもの。
    System.out.println("str1 == str5 is " + (str1==str5));  // 文字列リテラルとnewしたもの。

サンプルコードでは、String型の変数が5つ宣言されている。str1~str3が、文字列リテラルで初期値を指定したもの。str4とstr5がnewで新しくString型のインスタンスを生成したものである。

実行結果
str1.equals(str2) is true
str1.equals(str3) is true
str1.equals(str4) is true
str1.equals(str5) is true
str1 == str2 is true
str1 == str3 is true
str1 == str4 is false
str1 == str5 is false

上記実行結果のとおり、equalsメソッドで文字列の比較を行った場合、str1~str5のすべてが"Hello World!"を保持しているため、結果はすべてtrueとなっている。 しかし、==インスタンスを比較した場合は、文字列リテラル同士ではtrueとなっており、 文字列リテラルとnewしたものを比較した場合はfalseになっている。

この結果から、同じ内容の文字列リテラルは、同じインスタンスを指していることがわかる。

言語仕様

ちなみにこの動作はJavaの言語仕様として決められているようだ。

A string literal is a reference to an instance of class String (§4.3.1, §4.3.3). Moreover, a string literal always refers to the same instance of class String. This is because string literals - or, more generally, strings that are the values of constant expressions (§15.28) - are "interned" so as to share unique instances, using the method String.intern.

The Java Language Specification, Java SE 8 Edition - 3.10.5. String Literals

ざっくりというと、文字列リテラルは、String型のインスタンスへの参照を示すよ。 文字列リテラル(と定数式の結果の文字列)は、String型のinternメソッドを使って、ユニークなインスタンスを使うようにしてるよ。……と書いてあるみたい。

余談

ちなみにこんな時に注意。

独自クラス(String型のフィールドを持つ)のequalsメソッドの単体テストを書く場合、以下のようにしてしまうと、String型のフィールドが==で比較されていた、みたいな誤りが検出できないぞ。

   @Test
    public void testEquals() throws IOException {
        Hoge obj1 = new Hoge("dummy string");
        Hoge obj2 = new Hoge("dummy string");

        boolean actual1 = obj1.equals(obj2);
        assertThat(actual1, is(true));

        boolean actual2 = obj2.equals(obj1);
        assertThat(actual2, is(true));
    }
関連リンク

(おわり)