質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

1回答

995閲覧

プログラム中に直接書かれたString型の参照先

barbed

総合スコア10

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

1クリップ

投稿2019/06/16 13:44

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ページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

quickquip

2019/06/16 14:46 編集

そのように比較してはいけない(実用上比較することはない)ということはわかった上での、純粋に興味からの質問でしょうか。そのあたりも書いてあるといいかと思いました。
barbed

2019/06/16 15:49

当初は上記の比較方法を用いていましたが、当然思うような結果にはならず、誤りに気付き修正しました。 その上で疑問が残ったので、今回質問をいたしました。
guest

回答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
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2019/06/16 17:39

System.identityHashCode()を使って確かめると、リテラルは使う時に暗黙にintern()されます。それより先にintern()が実行されていれば、先の参照が有効になることがわかります。 String a = new String("ABC").intern(); String b = "ABC"; <-- ここで暗黙のintern()が実行され、上のaの参照が返る。 クラスロード時に自動的にintern()されるのではなく、リテラルが使われる時にintern()でした。
KSwordOfHaste

2019/06/16 17:58

ロード時にinternされるというのは確かに自分の推測ですが・・・ xebmeさんの主張の根拠がわかりませんでした。identityHashCodeでどのようにしてbへ代入された時点でinternされることが確認できたのでしょう?
KSwordOfHaste

2019/06/16 19:25

あ、いけない・・・回答の中で自分の推測を仕様として書いてしまってました!修正します。
xebme

2019/06/16 22:57

2つのプログラムが必要。以下の比較です。 --------------------------------- String a = new String("ABC").intern(); System.identityHashCode(a); String b = "ABC"; System.identityHashCode(b); --------------------------------- String b = "ABC"; System.identityHashCode(b); String a = new String("ABC").intern(); System.identityHashCode(a);
KSwordOfHaste

2019/06/17 01:55 編集

そのコードですと (A)クラスをロードした時点でintern (B)リテラルが代入された(使われた)時点でintern のどちらでも同じ結果になる気がしたので、少し変えて調べてみました public static void main(String[] args) { String a = null, b = null; switch (args[0].charAt(0)) { case 'a': a = "A"; b = "B"; break;//(A) case 'b': a = "B"; b = "A"; break;//(B) case 'c': b = "B"; a = "A"; break;//(C) case 'd': a = "C"; b = "D"; break;//(D) } System.out.printf("%x %x%n", System.identityHashCode(a), System.identityHashCode(b)); ... 結果をみると、なるほどロード時に全てのリテラルがinternされるのではないようでした。しかし不思議なのは上記4パターン全てaのidentityHashCode(概ねインスタンスのアドレスに準じた値)が同一の値になることです(java8,java12どちらも同様でした)。(A),(D)が同じことから参照された順番にinternされているように思えるのですが(A),(B),(C)の結果が全部同じになるのでプログラム実行前にinternされているようにも見えてしまいます。いったいどういう仕組みになってるのかわかりませんでした orz。 いずれにせよ自分の推測は間違いのようですので回答を訂正いたします。ご指摘ありがとうございました。
barbed

2019/06/17 09:24

ご回答ありがとうございました。 初学者なのですべてを理解できたわけではありませんが、constant poolによって今回のstr1のような文法で初期化されたものと同じ参照先になるのだと理解しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問