友人が StackOverflow に質問を投稿して、こう尋ねました:
Java の String オブジェクトは不変であることが知られていますが、次のコードを見てみましょう:
String s1 = "Hello World"; String s2 = "Hello World"; String s3 = s1.substring(6); System.out.println(s1); // Hello World System.out.println(s2); // Hello World System.out.println(s3); // World Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[])field.get(s1); value[6] = 'J'; value[7] = 'a'; value[8] = 'v'; value[9] = 'a'; value[10] = '!'; System.out.println(s1); // Hello Java! System.out.println(s2); // Hello Java! System.out.println(s3); // World
このコードの操作結果はなぜこのようになるのですか? s1 と s2 の値は変更されるのに、s3 の値は変更されないのはなぜですか?
回答 #1:
String オブジェクトは不変ですが、それはパブリック メソッドを呼び出してその値を変更できないことを意味します。
上記のコードは、リフレクション メカニズムを通じて通常の API をバイパスします。この方法では、列挙型の値を変更したり、整数型がオートボックス化されるときに使用されるルックアップ テーブルを変更したりすることもできます。
ここで、s1とs2は同じ内部文字列オブジェクトを指しているため、両方の値が変更されます。他の回答で述べたように、これはコンパイラによって実装されます。
s3 が変更されない理由には、私は本当に驚きました。s3 と s1 は同じ値の配列を共有していると考えていました (Java 7u6 より前のバージョンでは実際にそうでした)。ただし、String クラスのソース コードを見ると、部分文字列オブジェクトの値配列が元の文字列オブジェクトから (Arrays.copyOfRange(..) メソッドを使用して) コピーされていることがわかります。これが、s3 が変更されない理由です。
SecurityManager をインストールすると、悪意のあるコードがこの種の操作を実行するのを防ぐことができます。ただし、一部のライブラリ (ORM ツール、AOP ライブラリなど) の実装はこのリフレクション手法に依存していることに注意してください。
返信の冒頭で、String オブジェクトは実際には不変ではなく、「不変に見える」だけであると書きました。これにより、読者は String クラスの現在のバージョンがアクセス制限に関して無視されていると誤解する可能性がありますが、実際には、値の配列は private 修飾子と Final 修飾子を使用しています。したがって、開発者は注意する必要があります。Java では配列を不変として宣言することはできません。また、正しいアクセス修飾子が使用されている場合でも、配列をクラスの外部に公開することはできません。
このトピックは非常に話題になっているため、いくつかの高度な読書をお勧めします。2009 年の JavaZone カンファレンスでの Heinz Kabutz のリフレクション テクノロジに関するクレイジーなスピーチ。この記事では、リフレクション操作における一般的な問題と、リフレクション テクノロジに関するその他の内容について説明します。この記事は非常に優れており、非常にクレイジーです。
この記事では、特定のシナリオではリフレクション手法が役立つ理由を説明しますが、ほとんどの場合は使用を避けるべきです。
回答 #2:
Java では、String 型の 2 つの変数が同じ文字列に初期化されると、両方の変数に同じオブジェクト参照が割り当てられます。これが、式「Test1==Test2」が true を返す理由です。
String Test1="Hello World"; String Test2="Hello World"; System.out.println(test1==test2); // true
Test3 は、substring() メソッドによって作成された新しい String オブジェクトであり、Test1 と同じ値の配列を共有しません。 (注: オリジナル作成者の事務ミスにより、下の図の変数 test1 と test3 の最初の文字が大文字になっていません。読者の皆様はご注意ください。)
リフレクション技術を通じて String オブジェクトにアクセスし、値配列のポインタ:
Field field = String.class.getDeclaredField("value"); field.setAccessible(true);
Change この値配列の値は、配列ポインタを保持するすべての String オブジェクトの値を変更できるため、Test1 と Test2 の値が変更されています。ただし、Test3 は substring() メソッドによって作成された新しい String オブジェクトであるため、その値は変更されていません。