1. ホーム
  2. java

[解決済み] Java の文字列は本当に不変なのですか?

2022-03-23 20:32:08

質問

私たちは皆、次のことを知っています。 String はJavaでは不変ですが、次のコードを確認してください。

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  

なぜこのプログラムはこのような動作をするのでしょうか?また、なぜ s1s2 は変更されましたが s3 ?

解決方法は?

String は immutable* ですが、これは public API を使って変更できないことを意味するだけです。

ここでやっていることは、リフレクションを使って、通常のAPIを回避しているのです。同じように、enumの値を変更したり、Integer autoboxingで使用されるルックアップテーブルを変更したりすることができます。

では、その理由 s1s2 は、どちらも同じ内部文字列を参照しているということです。コンパイラはこれを行います(他の回答でも言及されています)。

その理由は s3 が行います。 ない を共有すると思っていたので、実はちょっと意外でした。 value 配列( 以前のバージョンのJavaでは Java 7u6 より前のバージョン)。しかし String を使用すると value の文字配列が実際にコピーされます。 Arrays.copyOfRange(..) ). そのため、変更されないのです。

をインストールすることができます。 SecurityManager のように、悪意のあるコードがそのようなことをしないようにするためです。しかし、いくつかのライブラリは、この種の反射トリックの使用に依存していることに留意してください(典型的なORMツール、AOPライブラリなど)。

*) 私は最初 String は実際にはイミュータブルではなく、単に"effective immutable"というだけです。の現在の実装では、これは誤解を招くかもしれません。 String というのは value の配列は、確かに private final . しかし、Javaでは配列を不変と宣言する方法がないため、適切なアクセス修飾子をつけても、クラスの外に公開しないように注意する必要があることは知っておく価値があります。


このトピックは圧倒的に人気があるようなので、さらにお勧めの記事を紹介します。 ハインツ・カブツの「リフレクション・マッドネス」トーク JavaZone 2009の記事で、OPの問題の多くと、他のリフレクションの...その...狂気を取り上げています。

なぜこの方法が役に立つのか、その理由を説明しています。そして、ほとんどの場合、それを避けるべき理由も。)