String型の参照先の比較をする以下のプログラムについての質問です。
Java
1class test{ 2 public static void main (String[] args) { 3 String str1 = "A" ; 4 String str2 = new String("A") ; 5 6 if(str1 == "A"){ 7 System.out.println("match"); 8 }else{ 9 System.out.println("mismatch"); 10 } 11 12 if(str2 == "A"){ 13 System.out.println("match"); 14 }else{ 15 System.out.println("mismatch"); 16 } 17 } 18} 19
これを実行すると、次のような結果になりました。
match mismatch
str1とstr2の参照先が別であるということは解るのですが、条件式中の"A"に関してはどのように設定されているのでしょうか。
また、str1とstr2の初期化の文法の違いが、今回どのように影響したのかも教えていただけると有難いです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/16 15:49
回答1件
0
ベストアンサー
ちょっと見慣れない内容かも知れませんが、Javaの内部の動きを観察するのにjavapによるbytecodeのダンプが役立つことがあるのでその例を挙げてみました。
sh
1$ javap -v -c test.class # 質問者さんのコード 2... 3Constant pool: 4 ... 5 #2 = String #21 // A 6 ... 7 8 public static void main(java.lang.String[]); 9 descriptor: ([Ljava/lang/String;)V 10 flags: (0x0009) ACC_PUBLIC, ACC_STATIC 11 Code: 12 stack=3, locals=3, args_size=1 13 0: ldc #2 // String A 14 2: astore_1 15 3: new #3 // class java/lang/String 16 6: dup 17 7: ldc #2 // String A 18 9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 19 12: astore_2 20 13: aload_1 21 14: ldc #2 // String A 22 16: if_acmpne 30
mainメソッドのbytecodeを眺めますと
String str1 = "A";
は0, 2の行であり、
0: constant poolの#2(つまり"A")をスタックにpush
2: astore_1でローカル変数str1へ格納
という具合になっています。また、
if (str1 == "A")
は13, 14, 16の行でして、
13: ローカル変数str1の中身をaload_1でスタックへpush
14: ldcでconstant poolの#2(つまり"A")をスタックへpush
16: スタックトップの2つの値を比較し違ったら#30へjump...
といった具合です。
ローカル変数の初期値として用いたStringリテラルと、比較対象としているStringリテラルはconstant pool上の同一のもの(#2のエントリー)が用いられていることがわかります。
一般にJavaのソースファイル上に出現する全てのStringリテラルはクラスがロードされたときに(訂正:以下に引用した仕様にはロードされたときとは書いてありません。ロードされたときにinternされるというのは単に私の推測に過ぎません)internされることになっています。internはStringクラスのインスタンスメソッドでして「同一の内容の文字列を同一のインスタンスに集約する」機能を持つものです。(ちなみに実装はMap<String, String>
を用いて一度登録したStringと同じ内容のStringインスタンスを同一のインスタンスへ写像することで実現されてます。割と単純です。)
それはJavaの言語仕様書(Java8であれば3.10.5 String Literals)に明記されています。
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.
実際に簡単なソースを動かしてみると実際にあらゆるクラスのリテラル(正確には文字列の定数式の結果)が同一のインスタンスになっていることが確認できます。
java
1package package1; 2 3public class P1 { 4 public static final String s1 = "A" + "BC"; 5}
java
1package package2; 2 3public class P2 { 4 public static void main(String[] args) { 5 String s1 = "ABC"; 6 String s2 = new String("ABC"); 7 System.out.printf("\"ABC\" == new String(\"ABC\") -> %s%n", s1 == s2); 8 String s3 = s2.intern(); 9 System.out.printf("\"ABC\" == new String(\"ABC\").intern() -> %s%n", s1 == s3); 10 System.out.printf("\"ABC\" == package1.P1.s1 -> %s%n", s1 == package1.P1.s1); 11 } 12}
sh
1$ javac package?/*.java 2$ java package2.P2 3"ABC" == new String("ABC") -> false 4"ABC" == new String("ABC").intern() -> true 5"ABC" == package1.P1.s1 -> true
クラスファイルはクラスごとに別々のファイルでありロードされるタイミングもまちまちなのですが、それがロードされるときにクラスローダーによって最初に挙げたconstant poolの中の文字列リテラルが自動的にinternされるような仕組みになっているだと思います。
訂正: xebmeさんご指摘により実験コードで調べてみたところロード時にconstant poolの中の文字列リテラルが無条件にinternされるわけではないようでした。では正確にいつinternされるかは...JVMの仕様書などもちょっと見てみたのですが残念ながらわかりませんでした。いずれにせよリテラルや文字列の定数式は(ある時点で)必ずinternされるということがポイントと思いますので、回答中のその点だけ注目していただければと思います。
ご指摘ありがとうございました >xebmeさん
投稿2019/06/16 17:10
編集2019/06/17 02:00総合スコア18392
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/16 17:39
2019/06/16 17:58
2019/06/16 19:25
2019/06/16 22:57
2019/06/17 01:55 編集
2019/06/17 09:24
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。