1. ホーム

[解決済み】Javaでfinalと宣言されている==の文字列を比較する。

2022-04-07 02:55:16

質問

Javaでの文字列について簡単な質問があります。次の簡単なコードは、2つの文字列を連結し、それらを == .

String str1="str";
String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

比較式 concat=="string" は以下を返します。 false を自明のこととして(私は equals()== ).


この2つの文字列が宣言されたとき final というように

final String str1="str";
final String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

比較式 concat=="string" を返すが、この場合は true . なぜ final は違いがあるのでしょうか?インターンプールと何か関係があるのでしょうか、それとも私が誤解しているだけなのでしょうか?

解決方法は?

を宣言すると String (これは 不変 ) 変数を final で初期化すると、これもコンパイル時定数式となり、その値が使用されるコンパイラでインライン化される。つまり、2番目のコード例では、値をインライン化した後、文字列の連結はコンパイラによって次のように変換されます。

String concat = "str" + "ing";  // which then becomes `String concat = "string";`

と比較すると "string" を使うと true というのは、文字列リテラルは インターン .

から JLS §4.12.4 - final 変数 :

プリミティブ型または型の変数 String ということです。 final と呼ばれ、コンパイル時の定数式(15.28節)で初期化されます。 定数変数 .

また、以下より JLS §15.28 - 定数式。

型のコンパイル時定数式 String は常に "interned"。 というメソッドを使って、一意なインスタンスを共有するようにします。 String#intern() .


最初のコード例では、このようなことはありません。 String 変数が final . つまり、これらはコンパイル時の定数式ではありません。そこでの連結操作は、実行時まで遅延され、その結果、新しい String オブジェクトを作成します。このことは、両者のバイトコードを比較することで確認できます。

最初のコード例 (非 final バージョン) は、以下のバイトコードにコンパイルされます。

  Code:
   0:   ldc     #2; //String str
   2:   astore_1
   3:   ldc     #3; //String ing
   5:   astore_2
   6:   new     #4; //class java/lang/StringBuilder
   9:   dup
   10:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
   13:  aload_1
   14:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  aload_2
   18:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   21:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   24:  astore_3
   25:  getstatic       #8; //Field java/lang/System.out:Ljava/io/PrintStream;
   28:  aload_3
   29:  ldc     #9; //String string
   31:  if_acmpne       38
   34:  iconst_1
   35:  goto    39
   38:  iconst_0
   39:  invokevirtual   #10; //Method java/io/PrintStream.println:(Z)V
   42:  return

明らかに保存している string を2つの別々の変数で使用し StringBuilder で連結操作を行う。

一方、2つ目のコード例では ( final バージョン) はこのように見えます。

  Code:
   0:   ldc     #2; //String string
   2:   astore_3
   3:   getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   6:   aload_3
   7:   ldc     #2; //String string
   9:   if_acmpne       16
   12:  iconst_1
   13:  goto    17
   16:  iconst_0
   17:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
   20:  return

そこで、最後の変数を直接インライン化し、Stringを作成します。 string によって読み込まれます。 ldc の操作で、ステップ 0 . 次に、2番目の文字列リテラルが ldc の操作で、ステップ 7 . これは、新しい String オブジェクトを実行時に作成します。Stringはコンパイル時に既に知られており、それらはインターンされる。