JLS
JLS 7 3.10.5はそれを定義し、実際的な例を示します。
さらに、文字列リテラルは常にStringクラスの同じインスタンスを参照します。これは、文字列リテラル、またはより一般的には定数式(§15.28)の値である文字列が、メソッドString.internを使用して一意のインスタンスを共有するように「インターン」されるためです。
例3.10.5-1。文字列リテラル
コンパイル単位(§7.3)で構成されるプログラム:
package testPackage;
class Test {
public static void main(String[] args) {
String hello = "Hello", lo = "lo";
System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");
System.out.print((hello == ("Hel"+"lo")) + " ");
System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());
}
}
class Other { static String hello = "Hello"; }
そしてコンパイルユニット:
package other;
public class Other { public static String hello = "Hello"; }
出力を生成します:
true true true true false true
JVMS
JVMS 7 5.1によると、インターンは専用のCONSTANT_String_info
構造体を使用して魔法のように効率的に実装されています(一般的な表現を持つ他のほとんどのオブジェクトとは異なります)。
文字列リテラルは、クラスStringのインスタンスへの参照であり、クラスまたはインターフェースのバイナリ表現のCONSTANT_String_info構造(§4.4.3)から派生します。CONSTANT_String_info構造体は、文字列リテラルを構成するUnicodeコードポイントのシーケンスを提供します。
Javaプログラミング言語では、同じ文字列リテラル(つまり、同じコードポイントのシーケンスを含むリテラル)がクラスStringの同じインスタンスを参照する必要があります(JLS§3.10.5)。さらに、メソッドString.internが任意の文字列で呼び出された場合、結果は、その文字列がリテラルとして表示された場合に返されるのと同じクラスインスタンスへの参照になります。したがって、次の式の値はtrueでなければなりません。
("a" + "b" + "c").intern() == "abc"
文字列リテラルを導出するために、Java仮想マシンはCONSTANT_String_info構造体によって与えられたコードポイントのシーケンスを調べます。
メソッドString.internが、CONSTANT_String_info構造体によって指定されたものと同一のUnicodeコードポイントのシーケンスを含むクラスStringのインスタンスで以前に呼び出された場合、文字列リテラル派生の結果は、クラスStringの同じインスタンスへの参照です。
それ以外の場合、CONSTANT_String_info構造体によって指定されたUnicodeコードポイントのシーケンスを含むStringクラスの新しいインスタンスが作成されます。そのクラスインスタンスへの参照は、文字列リテラルの派生の結果です。最後に、新しいStringインスタンスのインターンメソッドが呼び出されます。
バイトコード
OpenJDK 7バイトコードを逆コンパイルして、実際のインターンを見てみましょう。
逆コンパイルすると:
public class StringPool {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a);
System.out.println(b);
System.out.println(a == c);
}
}
定数プールにあります:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
とmain
:
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/String
9: dup
10: ldc #2 // String abc
12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
方法に注意してください:
0
and 3
:同じldc #2
定数がロードされます(リテラル)
12
:新しい文字列インスタンスが作成されます(#2
引数として)
35
:a
とc
通常のオブジェクトとして比較されますif_acmpne
定数文字列の表現は、バイトコードの魔法です。
上記のJVMSの引用は、Utf8が指すものが同じである場合は常に、同一のインスタンスがによってロードされることを示しているようldc
です。
私はフィールドに対して同様のテストを行いました、そして:
static final String s = "abc"
ConstantValue属性を介して定数テーブルを指します
- 非最終フィールドにはその属性はありませんが、次のように初期化できます
ldc
結論:文字列プールの直接バイトコードサポートがあり、メモリ表現が効率的です。
おまけ:それを、バイトコードを直接サポートしていない(アナログではない)Integerプールと比較してくださいCONSTANT_String_info
。