1. 文字列の問題
文字列は実際、日常のコーディング作業に非常に役立ち、比較的簡単に使用できるため、特に詳しく研究した人はほとんどいません。一方、面接や筆記試験では、より踏み込んだ難しい質問が行われることがよくあります。採用の際、私は候補者に関連する質問をすることがありますが、これは特に正確で詳細な質問をする必要があるという意味ではありません。1 つは基本的な知識の理解をテストすることです。 2 つ目は、JAVA の基礎知識をテストすることです。2 つ目は、技術に対する受験者の態度をテストすることです。
次のプログラムが何を出力するか見てみましょう?すべての質問に正しく答えられ、その理由がわかれば、この記事はあなたにとってあまり意味がありません。答えが間違っている場合、または原理があまり明確でない場合は、この記事を詳しく見て、各プログラムの結果とその結果が出力される根深い理由を明確に理解するのに役立ちます。
コード セグメント 1:
package com.paddx.test.string; public class StringTest { public static void main(String[] args) { String str1 = "string"; String str2 = new String("string"); String str3 = str2.intern(); System.out.println(str1==str2);//#1 System.out.println(str1==str3);//#2 } }
コード セグメント 2:
package com.paddx.test.string; public class StringTest01 { public static void main(String[] args) { String baseStr = "baseStr"; final String baseFinalStr = "baseStr"; String str1 = "baseStr01"; String str2 = "baseStr"+"01"; String str3 = baseStr + "01"; String str4 = baseFinalStr+"01"; String str5 = new String("baseStr01").intern(); System.out.println(str1 == str2);//#3 System.out.println(str1 == str3);//#4 System.out.println(str1 == str4);//#5 System.out.println(str1 == str5);//#6 } }
コード セグメント 3 (1):
package com.paddx.test.string; public class InternTest { public static void main(String[] args) { String str2 = new String("str")+new String("01"); str2.intern(); String str1 = "str01"; System.out.println(str2==str1);//#7 } }
コード セグメント 3 (2):
package com.paddx.test.string; public class InternTest01 { public static void main(String[] args) { String str1 = "str01"; String str2 = new String("str")+new String("01"); str2.intern(); System.out.println(str2 == str1);//#8 } }
説明の便宜上、上記のコードの私の出力を示します。 #1 ~#8 によるコード化が行われ、以下の青字部分が結果となります。
2. 文字列の詳細な分析
1. コードセグメント1の分析
文字列は基本型に属しませんが、基本型と同様にリテラルを介して直接割り当てることもできます。新しいオブジェクトを通じて文字列を生成します。ただし、リテラル代入による文字列の生成と new の間には本質的な違いがあります。
リテラル代入によって文字列を作成する場合、同じ文字列が 定数プールにすでに存在するかどうかが最初に検索されます。すでに存在する場合は、スタック内の reference は文字列を直接指します。存在しない場合は、定数プール内に文字列が生成され、スタック内の参照がその文字列を指します。 new を通じて文字列を作成すると、文字列オブジェクトがヒープに直接生成されます (JDK 7 以降、HotSpot は定数プールを永続世代からヒープに転送しました。詳細については、「JDK8 メモリ モデル 」を参照してください) - 消えた PermGen」の記事)、スタック内の参照はオブジェクトを指しています。ヒープ内の文字列オブジェクトの場合、 intern() メソッドを通じて文字列を定数プールに追加し、定数への参照を返すことができます。
これで、コード セグメント 1 の結果を明確に理解できるはずです。 結果 #1: str1 は文字列内の定数を指し、str2 はヒープ内に生成されたオブジェクトであるため、str1==str2 は false を返します。 結果 #2: str2 は intern メソッドを呼び出し、str2 の値 (「string」) を定数プールにコピーしますが、その文字列は定数プール (つまり、str1 が指す文字列) にすでに存在します。したがって、文字は直接 String 参照として返されるため、str1==str2 は true を返します。 以下はコード セグメント 1 の実行結果です:2. コード セグメント 2 の分析
コード セグメント 2 の結果については、StringTest01.class ファイルを逆コンパイルすると理解しやすくなります。 定数プールの内容(部分): 実行命令(部分、2列目の#+ordinalが定数プールの項目に対応): 上記の実行処理を説明する前に、まず、 2 つの命令: ldc : 実行時定数プールから項目をプッシュし、指定された項目の参照を定数プールからスタックにロードします。 astore_変数 に割り当てます。
ここで、コードセグメント 2 の実行プロセスの説明を開始します: 0: ldc #2: 定数プールの 2 番目の項目 (「baseStr」) をスタックにロードします。 2: astore_1 : 1 の参照を最初のローカル変数、つまり StringbaseStr = "baseStr" に割り当てます 3: ldc #2: 定数プールの 2 番目の項目 ("baseStr") を真ん中をスタックします。 5: astore_2 : 3 の参照を 2 番目のローカル変数、つまり、final String に代入します。 6: ldc #3: 定数プールの 3 番目の項目 ("baseStr01") をロードします。スタック内にあります。 8: astore_3: 6 の参照を 3 番目のローカル変数、つまり String str1="baseStr01";に割り当てます。
9: ldc #3: 定数プールの 3 番目の項目 (「baseStr01」) をスタックにロードします。
11: astore 4: 9 の参照を 4 番目のローカル変数に割り当てます: String str2="baseStr01";
結果 #3: str1 と str2 は両方とも定数を指しているため、str1==str2 は確実に true を返します。プール内の参照アドレス。実際、JAVA 1.6 以降では、コンパイル段階で定数文字列の「+」演算が文字列に直接合成されます。
13: 新しい #4: StringBuilder のインスタンスを生成します。
16: dup: 13で生成されたオブジェクトの参照をコピーし、スタックにプッシュします。
17: invokespecial #5: 定数プールの 5 番目の項目である StringBuilder.
上記の 3 つの命令は、StringBuilder オブジェクトを生成するために使用されます。
20: aload_1 : 最初のパラメータの値「baseStr」をロードします
21: invokevirtual #6: StringBuilder オブジェクトの append メソッドを呼び出します。
24: ldc #7: 定数プールの 7 番目の項目 (「01」) をスタックにロードします。
26: invokevirtual #6: StringBuilder.append メソッドを呼び出します。
29: invokevirtual #8: StringBuilder.toString メソッドを呼び出します。
32: astore 5: 29の結果参照の代入を5番目のローカル変数、つまり変数str3への代入に変更します。
結果 #4: str3 は実際には stringBuilder.append() によって生成された結果であるため、str1 と等しくなく、結果は false を返します。
34: ldc #3: 定数プールの 3 番目の項目 (「baseStr01」) をスタックにロードします。
36: astore 6: 34 の参照を 6 番目のローカル変数、つまり str4="baseStr01" に割り当てます。
結果 #5: str1 と str4 は両方とも定数プール内の 3 番目の項目を指しているため、str1 ==str4 は true を返します。ここでは、final フィールドの場合はコンパイル時に定数の置換が直接実行されるのに対し、non-final フィールドの場合は実行時に代入処理が実行されるという現象も見られます。 38: 新しい #9: String オブジェクトを作成します
41: DUP: 参照をコピーし、スタックと同様に押します。
42: ldc #3: 定数プールの 3 番目の項目 (「baseStr01」) をスタックにロードします。
44: invokespecial #10: String."
47: invokevirtual #11: String.intern メソッドを呼び出します。
38から41に対応するソースコードはnew String(“baseStr01″).intern()です。
50: astore 7: ステップ 47 で返された結果を変数 7 に代入します。つまり、str5 は定数プール内のbaseStr01 の位置を指します。
結果 #6: str5 と str1 は両方とも定数プール内の同じ文字列を指しているため、str1==str5 は true を返します。
コード セグメント 2 を実行すると、出力結果は次のようになります:
3. コード セグメント 3 の分析:コード セグメント 3 の実行結果は、JDK 1.6 と JDK 1.7 で異なります。まず実行結果を見て、その理由を説明しましょう:
JDK 1.6 での実行結果:
JDK 1.7 での実行結果:
コードセグメント 1 の分析によると、単純に JDK 1.6 の結果を取得する必要があります。str2 と str1 は元々異なる場所を指しているため、false を返す必要があります。
奇妙な問題は、JDK 1.7 以降、最初のケースでは true が返されるが、位置を変更すると返される結果が false になることです。この主な理由は、JDK 1.7 以降、HotSpot が定数プールを永続世代からメタスペースに移動したため、JDK 1.7 以降のインターン メソッドは実装において比較的大きな変更を受けています。まだ使用されている場合は、定数プールに既に存在するかどうかを確認します。存在する場合は、対応する文字列が見つからない場合と異なります。定数プールの場合、文字列は定数プールにコピーされ、元の文字列への参照のみが定数プール内に生成されます。つまり:
結果 #7: 最初のケースでは、定数プールに文字列 "str01" が存在しないため、ヒープ内の "str01" への参照が定数プールに生成され、リテラル代入を実行するときに、定数プールにはすでに存在しているため、参照を直接返すだけです。したがって、str1 と str2 は両方ともヒープ内の文字列を指し、true を返します。 結果 #8: 位置を交換した後、リテラル代入 (String str1 = "str01") を実行するときに定数プールが存在しないため、str1 は定数プール内の位置を指し、str2 は定数プール内の位置を指します。 heap オブジェクトの場合、intern メソッドが実行されると、str1 と str2 には影響しないため、false が返されます。 【関連おすすめ】
以上がJava の intern() メソッドの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。