55
class Test {
    public static void main(String...args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1.intern());
        String s2 = "Goodmorning";
        if (s1 == s2) {
            System.out.println("both are equal");
        }
    }
}

このコードは、Java6とJava7で異なる出力を生成します。Java6ではs1==s2条件が返さfalseれ、Java7では条件がs1==s2返されますtrue。なんで?

このプログラムがJava6とJava7で異なる出力を生成するのはなぜですか?

4

9 に答える 9

27

JDK7は以前とは違う方法でインターンを処理しているようです。
ビルド1.7.0-b147でテストしたところ、「両方が等しい」と表示されましたが、1,6.0_24で(同じバイトコード)実行すると、メッセージが表示されません。
またString b2 =...、ソースコードのどこに行があるかによっても異なります。次のコードもメッセージを出力しません。

class Test {
   public static void main(String... args) {
      String s1 = "Good";
      s1 = s1 + "morning";

      String s2 = "Goodmorning";
      System.out.println(s1.intern());  //just changed here s1.intern() and the if condition runs true   

      if(s1 == s2) {
         System.out.println("both are equal");
      } //now it works.
   }
}

intern文字列のプールで文字列が見つからなかった後、実際のインスタンスs1をプールに挿入しているようです。JVMは、s2の作成時にそのプールを使用しているため、s1と同じ参照を取得します。一方、s2が最初に作成された場合、その参照はプールに格納されます。
これは、インターンされた文字列をJavaヒープの永続的な生成から移動した結果である可能性があります。

ここにあります:JDK7で対処される重要なRFE

JDK 7では、インターンされた文字列はJavaヒープの永続的な世代では割り当てられなくなりましたが、代わりに、アプリケーションによって作成された他のオブジェクトとともに、Javaヒープの主要部分(若い世代と古い世代として知られています)に割り当てられます。 。この変更により、メインのJavaヒープに存在するデータが増え、永続生成のデータが少なくなるため、ヒープサイズの調整が必要になる場合があります。ほとんどのアプリケーションでは、この変更によりヒープ使用量に比較的小さな違いしか見られませんが、多くのクラスをロードしたり、String.intern()メソッドを多用する大規模なアプリケーションでは、より大きな違いが見られます。

それがバグであるかどうか、そしてどのバージョンからのものかわからない...JLS3.10.5は述べています

計算された文字列を明示的にインターンした結果は、同じ内容の既存のリテラル文字列と同じ文字列になります。

したがって、問題は、既存のものがコンパイル時または実行時にどのように解釈されるかです。「Goodmorning」は既存のものかどうかです。
私はそれが7以前に実装された方法を好みます...

于 2011-08-17T09:46:36.277 に答える
25

例から不要な詳細を省略しましょう。

class Test {
    public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

String#internブラックボックスと考えてみましょう。実行されたいくつかのテストケースに基づいて、実装は次のようになります。

Java 6:
プールにに等しいオブジェクトが含まれている場合はthis、そのオブジェクトへの参照を返します。そうでない場合は、新しい文字列(に等しいthis)を作成し、プールに配置して、作成されたインスタンスへの参照を返します。

Java 7:
プールに等しいオブジェクトが含まれている場合はthis、そのオブジェクトへの参照を返します。それ以外の場合thisは、プールに入れて、を返しthisます。

Java6もJava7も、メソッドのコントラクトを破ることはありません。

新しいインターンメソッドの動作は、このバグの修正の結果であるようです:http: //bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962931

于 2011-08-28T23:59:56.190 に答える
9

==参照を比較します。インターンメソッドは、同じ値の文字列が同じ参照を持つことを確認します。

String.internメソッドのjavadocは、次のように説明しています。

public String intern()

文字列オブジェクトの正規表現を返します。

最初は空の文字列のプールは、Stringクラスによってプライベートに維持されます。

インターンメソッドが呼び出されたときに、equals(Object)メソッドによって決定されたこのStringオブジェクトに等しい文字列がプールにすでに含まれている場合、プールからの文字列が返されます。それ以外の場合、このStringオブジェクトはプールに追加され、このStringオブジェクトへの参照が返されます。

したがって、任意の2つの文字列sおよびtについて、s.equals(t)が真である場合に限り、s.intern()== t.intern()が真になります。

すべてのリテラル文字列と文字列値の定数式がインターンされます。文字列リテラルは、Java言語仕様の§3.10.5で定義されています

戻り値:この文字列と同じ内容の文字列ですが、一意の文字列のプールからのものであることが保証されています。

したがって、インターンなしで、コンパイラはJavaコードの定数を調べ、そこから定数プールを構築します。Stringクラスによって維持される別のプールがあり、インターンは渡された文字列をプールに対してチェックし、参照が一意であることを確認します(==が機能するように)。

于 2011-08-15T13:16:57.773 に答える
7

jdk6の場合: String s1="Good";定数プールに文字列オブジェクト「Good」を作成します。

s1=s1+"morning";定数プールに別のStringオブジェクト「morning」を作成しますが、今回は実際にはJVMが次のことを行いますs1=new StringBuffer().append(s1).append("morning").toString();

演算子newがヒープ内にオブジェクトを作成するため、の参照s1は定数プールではなくヒープのものString s2="Goodmorning";であり、参照がに格納されている定数プールに文字列オブジェクト「Goodmorning」を作成しますs2

したがって、if(s1==s2)条件はfalseです。

しかし、jdk7では何が起こりますか?

于 2011-08-17T03:35:48.270 に答える
6

最初のケース:

切り取られた最初のコードでは、実際には文字列のプールに3つの文字列を追加しています。1. s1 = "Good"
2. s1 = "Goodmorning"(連結後)3。s2 = "Goodmorining"

if(s1 == s2)を実行している間、オブジェクトは同じですが、参照が異なるため、falseになります。

2番目のケース:

この場合、s1.intern()を使用しています。これは、equals(Object)メソッドによって決定されたこのStringオブジェクトと等しい文字列がプールにすでに含まれている場合、プールからの文字列が返されることを意味します。それ以外の場合、このStringオブジェクトはプールに追加され、このStringオブジェクトへの参照が返されます。

  1. s1="良い"
  2. s1 = "Goodmorning"(連結後)
  3. 文字列s2="Goodmorning"の場合、新しい文字列はプールに追加されず、s2の既存の文字列の参照を取得します。したがって、if(s1 == s2)はtrueを返します。
于 2011-08-18T08:21:15.183 に答える
5

を使用する必要がありますs1.equals(s2)。オブジェクトで使用==するStringと、オブジェクト参照自体が比較されます。

編集:2番目のコードスニペットを実行すると、「両方が等しい」と出力されません。

Edit2:'=='を使用すると参照が比較されることを明確にしました。

于 2011-08-15T13:15:04.227 に答える
4

文字列を比較するには、主に4つの方法があります。

  1. "==演算子":文字列オブジェクトの参照変数を比較するだけです。したがって、文字列の作成方法によっては、予期しない結果が生じる可能性があります。つまり、Stringクラスのコンストラクターを使用するか、単に二重引用符を使用することで、メモリの取得方法が異なります(ヒープとプールでそれぞれ)。
  2. 「equals(Object)メソッド」:これはオブジェクトクラスのメソッドであり、文字列クラスによってオーバーロードされます。文字列全体を比較し、大文字と小文字を区別します。
  3. "equalsIgnoreCase(String)メソッド":これは文字列クラスのメソッドであり、文字列全体を比較し、大文字と小文字を区別しません。
  4. 「compares(String)メソッド」:両方の文字列を文字ごとに比較し、戻り値が0の場合は差を返します。これは、文字列が等しいことを意味します。
于 2011-08-15T15:47:55.020 に答える
3

2つの文字列を比較する場合は常に、参照ではなくオブジェクトを比較するため==、使用しないでください。eqauls()

string1.equals(string2);
于 2011-08-15T13:17:43.180 に答える
2

結果コードはランタイムに依存します:

class Test {
     public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

このように書く場合:

class Test {
     public static void main(String... args) {
        String s = "GoodMorning";
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints false for both jdk7 and jdk6.
    }
}

その理由は、'ldc #N'(定数プールから文字列をロード)とString.intern()の両方がホットスポットJVMでStringTableを使用するためです。詳細については、プールの英語の記事を書きました:http: //aprilsoft.cn/blog/post/307.html

于 2012-05-26T02:11:56.267 に答える