JavaでStringが不変なのはなぜですか?


177

インタビューで、Stringが不変である理由を尋ねられました

私はこのように答えました:

のようにJavaで文字列を作成するとString s1="hello";、オブジェクトは文字列pool(hello)に作成され、s1helloを指します。今度は、もう一度String s2="hello";作成すると、別のオブジェクトは作成されませんが 、JVMが最初にチェックするため、s2が指します。同じオブジェクトが文字列プールに存在するかどうか。存在しない場合は、新しいオブジェクト のみが作成され、それ以外の場合は作成されません。hello

ここで、javaが文字列の変更を許可しているとすると、s1をに変更するとhello worlds2の値も変更されhello world、java文字列は不変になります。

私の答えが正しい間違っているかを誰でも教えてくれますか?


46
なぜ答えるのがいつも難しいの。最も正しい答えは、おそらく次のとおりです。言語デザイナーはそれが良い考えだと考えたからです。
ケッピル2014年

1
また、この回答を

3
あなたの答えは要点ではありません。C ++ std::stringは変更可能ですが、文字列プールもあります(より正確には、文字配列プール)。
Siyuan Ren

1
@rocking正直なところ、それが正しいかどうかは、彼らがそれをどのように読むかによって異なります。実は文字列は不変なので、Javaは文字列プールを持つことができます。文字列を可変にすることを決定した場合、文字列プールを使用しなかったでしょう。したがって、「文字列プール、したがって不変の文字列」と言っても正確ではない可能性があります。それはもっと逆です。理由の不変の文字列を選択するには、以下のとおりですし、文字列プールは、作業戦略であるため、そのことを。それでも、あなたの答えは正しくありません。完全ではないようです。あなたは待つだけで彼らの言うことを見る必要があります。
Jason C

34
なぜこの質問が閉じられたのか理解できません。想定される関連する回答はJavaに関するものではなく、この質問の主な主題である「なぜ」に対処していません。私にとって、これは私たちが無責任なコミュニティが質問について行動するケースの1つです。彼らは何も知りません。再開するように指定しました。
Edwin Dalorzo

回答:


163

String いくつかの理由で不変です、ここに要約があります:

  • 安全保障:パラメータは通常String、ネットワーク接続、データベース接続のURL、ユーザー名/パスワードなどのように表されます。変更可能な場合、これらのパラメータは簡単に変更できます。
  • 同期と並行性:文字列を不変にすると、自動的にスレッドセーフになり、同期の問題が解決されます。
  • キャッシング:コンパイラーがStringオブジェクトを最適化すると、2つのオブジェクトが同じ値(a = "test"、およびb = "test")を持つ場合、1つの文字列オブジェクトのみが必要であることがわかります(aとbの両方に対して、これら2つは同じオブジェクトを指す)。
  • クラスローディングクラスローディングのString引数として使用されます。変更可能な場合、誤ったクラスがロードされる可能性があります(変更可能なオブジェクトはその状態を変更するため)。

Stringとはいえ、の不変性は、そのパブリックAPIを使用して変更できないことを意味します。実際には、リフレクションを使用して通常のAPIをバイパスできます。ここで答えを見てください

あなたの例では、もしString変更可能だったなら、次の例を考えてみましょう:

  String a="stack";
  System.out.println(a);//prints stack
  a.setValue("overflow");
  System.out.println(a);//if mutable it would print overflow

14
どのようにしてセキュリティに影響を与えることができますか?
Archit Maheshwari 2015

2
可能であれば、誰かがクラスのロードを例で説明できますか?
Viraj 2015年

6
セキュリティに関しては、接続パラメーターの変更に興味がある場合、実行時に(デバッガーなどを使用して)簡単です。クラスのロードに関して、String変更可能な場合、クラスローダーは渡された文字列を取得してコピーを作成し、そのコピーは変更しません。可変に問題のことを考えていたときjava.lang.Stringのことは可変であるため、(C ++は、この問題を解決する方法を考えるstd::stringのを。
限定的贖罪

セキュリティに関して、プログラムの実行中に可変文字列をどのように変更できますか?
MasterJoe2

Stringは不変であるため、そのハッシュコードは作成時にキャッシュされ、再度計算する必要はありません。
Abdul Alim Shakir、

45

Java開発者は、次のアスペクト設計、効率、およびセキュリティのため、文字列は不変であると判断します。

設計 文字列は、「String Intern pool」として知られるJavaヒープの特別なメモリ領域に作成されます。新しいStringを作成している間(String()コンストラクターや、新しいStringオブジェクトを作成するためにString()コンストラクターを内部で使用するその他のString関数を使用している場合は除く)、String()コンストラクターは、プール内に常に新しい文字列定数を作成します。メソッドintern()を呼び出す)をプールを検索し、それがすでに存在するかどうかを確認します。存在する場合は、既存のStringオブジェクトの参照を返します。文字列が不変でない場合、1つの参照で文字列を変更すると、他の参照の値が正しくなくなります。

による DZoneに関するこの記事に:

安全保障 Stringは、ネットワーク接続、ファイルのオープンなど、多くのJavaクラスのパラメーターとして広く使用されています。Stringが不変でない場合、接続またはファイルが変更され、深刻なセキュリティ上の脅威につながります。パラメータが文字列であるため、可変文字列はReflectionでもセキュリティの問題を引き起こす可能性があります。

効率性 文字列のハッシュコードはJavaで頻繁に使用されます。たとえば、HashMapで。不変であることは、ハッシュコードが常に同じであることを保証するため、変更を心配することなくハッシュコードをキャッシュできます。つまり、使用するたびにハッシュコードを計算する必要はありません。


7
文字列プールの理解が正しくありません。文字列定数はインターンプールで作成されますが、同じテキストを持つ複数の文字列オブジェクトを持つことは完全に可能です。文字列が不変であることはプールを可能にすることに同意しますが、あなたが述べたほど多くのプールはありません。
Jon Skeet、2015年

@JonSkeetあなたは正しいです。文字列s1 = new String( "test"); ステートメントは、メソッドintern()を呼び出さない限り、インターンプールに新しい文字列定数を作成します。文字列インターンプールに関する私の知識を深めていただきありがとうございます。
Alex Mathew、2015年

2
それは単に文字列コンストラクタを使用するだけではありません- 新しい文字列を作成するほとんどすべてのもの、たとえばサブ文字列、分割、連結などは新しい文字列を作成します。コンパイル時の定数は、ここでは特殊なケースであり、標準ではありません...
Jon Skeet

@JonSkeet substring()、concat()、replace()などは、新しい文字列オブジェクトを作成するために内部的に文字列コンストラクターを使用します。私の答えを改善していただきありがとうございます。
Alex Mathew、2015年

2
@JonSkeet-これらすべての答えは、不変性が「セキュリティ」を向上させると述べていますが、その方法は説明していません。それらはすべて、役に立たない漠然としたdzone記事にリンクしています。回答/リンクは、コードの実行中に変更可能な文字列がどのように変更されるかを説明していません。説明していただけますか?
MasterJoe2

25

設計中にJavaデザイナーが実際に何を考えていたのかわからない Stringわかりませんが、これらの理由は、文字列の不変性から得られる利点に基づいてのみ結論を出すことができます。

1.文字列定数プールの存在

文字列が文字列定数プールに格納される理由で説明したように、すべてのアプリケーションは多すぎる文字列オブジェクトを作成し、JVMが最初に大量の文字列オブジェクトを作成してからガベージコレクションを実行しないようにします。JVMは、すべての文字列オブジェクトを文字列定数プールと呼ばれる別のメモリ領域に格納し、そのキャッシュされたプールからオブジェクトを再利用します。

文字列リテラルを作成するたびに、JVMはそのリテラルが定数プールにすでに存在するかどうかを最初に確認し、存在する場合は、新しい参照がSCP内の同じオブジェクトをポイントし始めます。

String a = "Naresh";
String b = "Naresh";
String c = "Naresh";

上記の例では、値を持つ文字列オブジェクトはNaresh一度だけSCPに作成されますと、すべての参照abc同じオブジェクトを指しますが、どのような場合、我々はの変化作ってみるaなどをa.replace("a", "")

理想的にaは、価値Nreshbあるcべきですが、エンドユーザーとしてaのみ変更を行うため、変更しないでおく必要があります。そして、私たちは知っているabc我々は変化をするので、もしすべてが同じオブジェクトを指していますa、他の人も変更を反映すべきです。

しかし、文字列の不変性はこのシナリオから私たちを救い、文字列オブジェクトの不変性のために文字列オブジェクトNareshは決して変更されません。したがってa、文字列オブジェクトを変更するのではなく変更を加えると、NareshJVMは新しいオブジェクトを作成し、それに割り当ててからa、そのオブジェクトを変更します。

したがって、文字列プールは文字列の不変性のためにのみ可能であり、文字列が不変でなければ、文字列オブジェクトをキャッシュして再利用することは、変数が値を変更し、他の文字列を破損する可能性がないためです。

そして、それがJVMによって特別に処理され、特別なメモリ領域が与えられている理由です。

2.スレッドセーフティ

複数のスレッドがオブジェクトを操作している場合、オブジェクトはスレッドセーフと呼ばれますが、その状態を破壊することはできず、オブジェクトはいつでもすべてのスレッドに対して同じ状態を保持します。

私たちは不変オブジェクトを作成した後は誰も変更できないため、デフォルトではすべての不変オブジェクトがスレッドセーフになります。同期メソッドの作成など、スレッドセーフ対策を適用する必要はありません。

したがって、その不変の性質により、文字列オブジェクトは複数のスレッドで共有でき、多くのスレッドによって操作されている場合でも、その値は変更されません。

3.セキュリティ

すべてのアプリケーションで、ユーザーのユーザー名\パスワード、接続URLなどのいくつかのシークレットを渡す必要があります。通常、この情報はすべて文字列オブジェクトとして渡されます。

ここで、Stringが本質的に不変ではなかった場合、これらの値が変更される可能性があるため、アプリケーションに深刻なセキュリティ上の脅威を引き起こすと想定します。変数参照にアクセスできます。

4.クラスローディング

を使用したJavaでのリフレクションによるオブジェクトの作成」で説明したように、Class.forName("class_name")メソッドをしてクラスをメモリにロードし、他のメソッドを呼び出してそれを行うことができます。また、JVMでもこれらのメソッドを使用してクラスをロードします。

しかし、これらのすべてのメソッドがクラス名を文字列オブジェクトとして受け入れることがはっきりとわかる場合は、Javaクラスの読み込みで文字列が使用され、不変性によって、によって正しいクラスが読み込まれるというセキュリティが提供されClassLoaderます。

Stringが不変ではなく、ロードjava.lang.Objectを試みてorg.theft.OurObject、その間に変更され、すべてのオブジェクトが、誰かが不要なことに使用できる動作を持っているとします。

5. HashCodeキャッシング

オブジェクトに対してハッシュ関連の操作を実行する場合は、hashCode()メソッドをオーバーライドし、オブジェクトの状態を使用して正確なハッシュコードを生成する必要があります。オブジェクトの状態が変化している場合、つまり、そのハッシュコードも変化するはずです。

Stringは不変なので、1つの文字列オブジェクトが保持している値は決して変更されません。つまり、そのハッシュコードも変更されず、Stringクラスはオブジェクトの作成中にハッシュコードをキャッシュする機会が与えられます。

はい、文字列オブジェクトはオブジェクトの作成時にハッシュコードをキャッシュします。これにより、ハッシュコードを再計算する必要がなくなり、時間を節約できるため、関連する操作をハッシュするのに最適な候補になります。これが、文字列が主にHashMapキーとして使用される理由です。

JavaでStringが不変で最終的な理由についての詳細を読んでください。


1
セキュリティに関して-変更可能な文字列値はどのようにしてメモリ内で変更できますか?別の人が変数参照にアクセスするにはどうすればよいですか?
MasterJoe2 2018

参照へのアクセスを取得する方法についてではなく、誰かが参照へのアクセスを持っている場合はどうなりますか?前述のように、「文字列が本質的に不変でなかった場合、これらの値が変更される可能性があるため、アプリケーションに深刻なセキュリティ上の脅威を引き起こし、許可されている場合、誤って記述されたコードまたは他の人物によって変更される可能性があります。私たちの変数参照にアクセスできる人。」
Naresh Joshi

ここでどのように重要か。参照へのアクセスを取得できるかどうか。可能であれば、それを行うために使用できる1〜2のテクニック***(つまり、方法)を挙げられますか?それが不可能な場合、セキュリティに関するポイントは適用されません。***例-WebアプリのDB-> SQLインジェクションを攻撃する手法を1つ挙げてください。参照を攻撃するためのこのようなテクニックを知っていますか?
MasterJoe2 2018

前述のように、「コードの記述が間違っているか、変数の参照にアクセスできる別の人が行った変更が原因で発生する可能性があります」。たとえば、文字列が変更可能で、文字列の秘密の文字列を使用するいくつかのメソッドを記述していて、その文字列が間にある別のいくつかのメソッドに渡され、それらのメソッドの1つがユーザーによって記述されておらず、そのメソッドがいくつかの変更を加えたとします。これらのすべてのメソッドを呼び出した後、文字列はコントロールがメソッドを返し、その文字列を再び使用していますが、変更されています。
Naresh Joshi

2
所属を開示し、投稿を通じてサイトを宣伝する手段としてサイトを使用しないでください。参照してください。私は良い答えを書くにはどうすればよいですか?
イヴェット-モニカを

21

DZoneに関するこの記事によると最も重要な理由:

文字列定数プール ...文字列が変更可能な場合、1つの参照で文字列を変更すると、他の参照の値が正しくなくなります。

安全保障

文字列は、ネットワーク接続、ファイルのオープンなど、多くのJavaクラスのパラメーターとして広く使用されています。文字列が不変でない場合、接続またはファイルが変更され、深刻なセキュリティ上の脅威につながります。...

それがあなたを助けることを願っています。


@JasonC私はちょうど私の答えが間違っているかnot.Iかどうかを知りたい、既に面接に出席し、result.Ifを待っていた答えが、右、私は選択されていることを伝えました
ロッキング

1
私の知る限りでは、あなたの答えは正しいですが、不変ということは、参照がポインティング位置を変更することは決してないということです。インタビューに最適です。
JDGuide 2014年

1
ポイント#1を受け入れる場合、すべてのオブジェクトは不変でなければなりません。
nicomp

こんにちはJDeveloperです。私はあなたの回答を編集して、あなたの回答のソースに適切な帰属を与えました。コンテンツの逐語的コピーには常にブロック引用符を使用することを忘れないでください。ありがとう!
NickL 2017年

DZoneの記事には、Strignプールの操作に関する主要なエラーが含まれています。それは唯一の定数のために。Ergoが述べた理論的根拠は無効です。
ローンの侯爵

4

私はこの記事を読んで、JavaでStringが不変または最終である理由を読み、以下が最も重要な理由であると想定します。

Stringオブジェクトは文字列プールにキャッシュされるため、Javaでは文字列は不変です。キャッシュされた文字列リテラルは複数のクライアント間で共有されるため、常に1つのクライアントのアクションが他のすべてのクライアントに影響を与えるリスクがあります。


1

あなたが正しいです。StringJavaではString Poolリテラルの概念を使用しています。文字列が作成され、その文字列が既にプールに存在する場合、新しいオブジェクトを作成してその参照を返す代わりに、既存の文字列の参照が返されます。文字列が不変でない場合、1つの参照で文字列を変更すると、他の参照に対して誤った値を導きます。

もう1つ追加しStringます。不変なので、マルチスレッド化に対して安全であり、単一のStringインスタンスを異なるスレッド間で共有できます。これにより、スレッドセーフのための同期の使用が回避されますthread safe。文字列は暗黙的に使用されます。


0

文字列クラスとは、FINALそれを継承して基本構造を変更してStingを変更可能にするクラスを作成できないことを意味します。

提供されるStringクラスのインスタンス変数とメソッドのもう1つは、String一度作成されたオブジェクトを変更できないようなものです。

追加した理由は、文字列をまったく不変にしないということです。これはすべて、文字列がヒープに格納される方法を示しています。また、文字列プールはパフォーマンスに大きな違いをもたらします


11
クラスがfinalとして宣言されている場合、それはクラスを継承できないことを意味しますが、クラスのインスタンスフィールドを変更できないことを意味しないため、クラスは不変です。
ドミトリー・ビチェンコ、2014年

@Zeeshan:指定したサンプルクラスはすべて不変です。
Siyuan Ren

0

文字列は、マップコレクションのキーとして格納するために使用できるため、Sunマイクロシステムによって不変として指定されます。StringBufferは変更可能です。そのため、マップオブジェクトのキーとして使用できません


0

Javaで文字列が不変にされる最も重要な理由は、セキュリティの考慮です。次はキャッシングです。

効率、同時実行性、デザイン、文字列プールなど、ここで示されている他の理由は、文字列が不変にされたという事実から来ていると思います。たとえば 文字列は不変であり、その逆ではないため、文字列プールを作成できます。

ゴスリングのインタビューの筆記録はこちらで確認してください

戦略的な観点から、彼らはしばしばトラブルのない傾向があります。そして、結果をキャッシュするなど、通常、変更可能なものでは実行できない不変のもので実行できるものがあります。文字列をファイルオープンメソッドに渡す場合、またはユーザーインターフェイスでラベルのコンストラクターに文字列を渡す場合、一部のAPI(多くのWindows APIなど)では、文字の配列で渡します。そのオブジェクトの受信者は、オブジェクトのストレージの存続期間について何も知らないため、実際にそれをコピーする必要があります。また、オブジェクトが足元で変更されているかどうか、オブジェクトに何が起こっているのかを知りません。

オブジェクトを所有するかどうかわからないため、オブジェクトの複製をほぼ強制されることになります。そして、不変オブジェクトの素晴らしい点の1つは、答えは「そうです、もちろんそうです」です。誰がそれを変更する権利を持つかという所有権の問題は存在しないからです。

文字列を不変にしたのは、セキュリティでした。ファイルを開くメソッドがあります。あなたはそれに文字列を渡します。そして、OS呼び出しを実行する前に、あらゆる種類の認証チェックを実行しています。セキュリティチェックの後、OS呼び出しの前に、文字列を効果的に変更することに成功した場合、ブームに陥ります。しかし、文字列は不変なので、この種の攻撃は機能しません。その正確な例は、文字列が不変であることを本当に要求したものです


0

すばらしい答えに加えて、いくつかのポイントを追加したいと思いました。文字列arr1と同様に、配列は配列の先頭への参照を保持するため、2つの配列を作成しarr2arr2 = arr1このようなことを行うと、参照がarr2同じになるため、arr1一方の値を変更すると、たとえば、もう一方の値も変更されます。

public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4};
        int[] b = a;
        a[0] = 8;
        b[1] = 7;
        System.out.println("A: " + a[0] + ", B: " + b[0]);
        System.out.println("A: " + a[1] + ", B: " + b[1]);
        //outputs
        //A: 8, B: 8
        //A: 7, B: 7
    }
}

コードにバグを引き起こすだけでなく、悪意のあるユーザーが悪用することもできます。管理者パスワードを変更するシステムがあるとします。ユーザーは最初にを入力する必要がありnewPassword、次にがプログラムと同じoldPassword場合はによってパスワードを変更します。新しいパスワードが管理者パスワードと同じ参照を持っているとしましょう。そうでない場合、プログラマがユーザーがデータを入力する前に管理者パスワードを保持する変数を作成して、それが等しい場合はパスワードを変更します。oldPasswordadminPassadminPass = newPasswordtempoldPasswordtempadminPass = temp。新しいパスワードを簡単に入力でき、古いパスワードとabrracadabraは決して入力できないことを知っている人は、管理者権限を持っています。JVMは、すべてのオブジェクトの新しい文字列を作成し、そのためのメモリでユニークな場所を持っていて、ちょうど使用していることを行うことができない理由を文字列について学ぶ際にもう一つは、私は理解していなかったnew String("str");あなたが常に使用になりたくない理由がnewありますそれはメモリ効率が悪く、ほとんどの場合遅くなるため、より多くを読みます


0

HELLOが文字列の場合、に変更HELLOすることはできませんHILLO。このプロパティは不変性プロパティと呼ばれます。

HELLO文字列を指す複数のポインタ文字列変数を使用できます。

ただし、HELLOがchar配列の場合は、HELLOをHILLOに変更できます。例えば、

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

回答:

プログラミング言語には不変のデータ変数があるため、キーと値のペアのキーとして使用できます。文字列変数はキー/インデックスとして使用されるため、不変です。


-1

Security観点から、この実用的な例を使用できます。

DBCursor makeConnection(String IP,String PORT,String USER,String PASS,String TABLE) {

    // if strings were mutable IP,PORT,USER,PASS can be changed by validate function
    Boolean validated = validate(IP,PORT,USER,PASS);

    // here we are not sure if IP, PORT, USER, PASS changed or not ??
    if (validated) {
         DBConnection conn = doConnection(IP,PORT,USER,PASS);
    }

    // rest of the code goes here ....
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.