彼らString
がJavaと.NET(および他のいくつかの言語)で不変にすることを決めたのはなぜですか?彼らはなぜそれを変更可能にしなかったのですか?
String
。.NET では実際には内部で変更可能です。StringBuilder
.NETの2.0変異する文字列。ここでそのままにしておきます。
彼らString
がJavaと.NET(および他のいくつかの言語)で不変にすることを決めたのはなぜですか?彼らはなぜそれを変更可能にしなかったのですか?
String
。.NET では実際には内部で変更可能です。StringBuilder
.NETの2.0変異する文字列。ここでそのままにしておきます。
回答:
よる効果的なJavaの、第4章、73ページ、第2版:
「これには多くの理由があります。不変クラスは、可変クラスよりも設計、実装、使用が簡単です。エラーが発生しにくく、安全です。
[...]
" 不変オブジェクトは単純です。不変オブジェクトは、1つの状態、つまり作成された状態にすることができます。すべてのコンストラクターがクラス不変式を確立していることを確認すると、これらの不変式が常にtrueのままになることが保証されます。あなたの側の努力はありません。
[...]
不変オブジェクトは本質的にスレッドセーフです。同期は必要ありません。それらは、同時にアクセスする複数のスレッドによって破損することはありません。これは、スレッドセーフを実現するための最も簡単な方法です。実際、不変オブジェクトに対する別のスレッドの影響を監視できるスレッドはありません。したがって、 不変オブジェクトは自由に共有できます
[...]
同じ章の他の小さな点:
不変オブジェクトを共有できるだけでなく、その内部を共有することもできます。
[...]
不変オブジェクトは、変更可能か不変かにかかわらず、他のオブジェクトの優れた構成要素になります。
[...]
不変クラスの唯一の本当の欠点は、個別の値ごとに個別のオブジェクトが必要なことです。
report2.Text = report1.Text;
。次に、別の場所でテキストを変更しますreport2.Text.Replace(someWord, someOtherWord);
。これにより、最初のレポートだけでなく2番目のレポートも変更されます。
少なくとも2つの理由があります。
最初-セキュリティ http://www.javafaq.nu/java-article1060.html
Stringを不変にした主な理由はセキュリティでした。次の例を見てください。ログインチェック付きのファイルオープンメソッドがあります。このメソッドに文字列を渡して、呼び出しがOSに渡される前に必要な認証を処理します。Stringが変更可能だった場合、認証チェックの後、OSがプログラムからリクエストを取得する前に、なんらかの方法でコンテンツを変更することが可能でした。その後、任意のファイルをリクエストすることが可能です。したがって、ユーザーディレクトリでテキストファイルを開く権利があるが、なんとかしてファイル名を変更できたときにその場で「passwd」ファイルまたはその他のファイルを開くように要求できます。その後、ファイルを変更し、OSに直接ログインすることができます。
2番目-メモリ効率 http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
JVMは「文字列プール」を内部的に維持します。メモリ効率を上げるために、JVMはプールからStringオブジェクトを参照します。新しいStringオブジェクトは作成されません。したがって、新しい文字列リテラルを作成すると、JVMはプールがすでに存在するかどうかをプールにチェックインします。すでにプールに存在する場合は、同じオブジェクトへの参照を与えるか、プールに新しいオブジェクトを作成します。同じStringオブジェクトへの参照が多数あります。誰かが値を変更すると、すべての参照に影響します。それで、太陽はそれを不変にすることに決めました。
実際、Javaで文字列が不変である理由は、セキュリティとはあまり関係がありません。2つの主な理由は次のとおりです。
文字列は、非常に広く使用されているオブジェクトのタイプです。したがって、マルチスレッド環境での使用がほぼ保証されています。文字列は不変であり、スレッド間で文字列を共有しても安全です。不変文字列を使用すると、スレッドAから別のスレッドBに文字列を渡すときに、スレッドBがスレッドAの文字列を予期せず変更することがなくなります。
これは、マルチスレッドプログラミングのすでにかなり複雑なタスクを簡素化するだけでなく、マルチスレッドアプリケーションのパフォーマンスにも役立ちます。可変オブジェクトへのアクセスは、複数のスレッドからアクセスできる場合、何らかの方法で同期させて、別のスレッドによって変更されている間、あるスレッドがオブジェクトの値を読み取らないようにする必要があります。適切な同期は、プログラマーにとって正しく行うのが難しく、実行時に費用がかかります。不変オブジェクトは変更できないため、同期する必要はありません。
文字列のインターニングについて言及しましたが、Javaプログラムのメモリ効率の向上はわずかです。文字列リテラルのみがインターンされます。つまり、ソースコードで同じ文字列のみが同じ文字列オブジェクトを共有します。プログラムが動的に同じ文字列を作成する場合、それらは異なるオブジェクトで表されます。
さらに重要なことに、不変の文字列を使用すると、内部データを共有できます。多くの文字列操作の場合、これは基本となる文字の配列をコピーする必要がないことを意味します。たとえば、Stringの最初の5文字を取得するとします。Javaでは、myString.substring(0,5)を呼び出します。この場合、substring()メソッドは、myStringの基礎となるchar []を共有する新しいStringオブジェクトを作成するだけで、インデックス0で始まり、そのchar []のインデックス5で終わることを知っています。これをグラフィック形式にするには、次のようになります。
| myString |
v v
"The quick brown fox jumps over the lazy dog" <-- shared char[]
^ ^
| | myString.substring(0,5)
これにより、この種の操作は非常に安価になり、O(1)は元の文字列の長さにも、抽出する必要のある部分文字列の長さにも依存しないためです。多くの文字列は基礎となるchar []を共有できるため、この動作にはメモリの利点もあります。
char[]
は、かなり疑問の多い設計上の決定です。ファイル全体を単一の文字列に読み込み、1文字の部分文字列への参照を維持する場合、ファイル全体をメモリに保持する必要があります。
String.substring()
上記のコメントで言及されている問題を回避するために、完全なコピーを実行します。Java 8では、char[]
共有を可能にする2つのフィールド、つまりcount
およびoffset
が削除され、Stringインスタンスのメモリフットプリントが削減されました。
「なぜXが変更可能である必要があるのですか?」すでにプリンセスフラッフによって言及されている利点があるため、デフォルトは不変性に設定することをお勧めします。何かが変更可能であることは例外であるべきです。
残念ながら、現在のプログラミング言語のほとんどはデフォルトで可変性になっていますが、将来的にはデフォルトが不変性により多くなることを期待しています(次の主流のプログラミング言語のウィッシュリストを参照)。
うわー!ここの誤報は信じられません。String
不変であることは、セキュリティには何の意味もありません。実行中のアプリケーションのオブジェクトに誰かがすでにアクセスしている場合(誰かがString
アプリを「ハッキング」しないようにする場合は、これを想定する必要があります)、ハッキングに利用できる機会は他にもたくさんあります。
の不変性が String
スレッドの問題に対処し。うーん...私は2つの異なるスレッドによって変更されているオブジェクトを持っています。どうすれば解決できますか?オブジェクトへのアクセスを同期しますか?Naawww ...誰もオブジェクトを変更させないようにしましょう-面倒な同時実行の問題はすべて修正されます!実際、すべてのオブジェクトを不変にして、Java言語から同期化された構造を削除することができます。
本当の理由(上記で他の人が指摘)はメモリの最適化です。どのアプリケーションでも、同じ文字列リテラルを繰り返し使用することはよくあります。実際、数十年前に多くのコンパイラーがString
リテラルの単一のインスタンスのみを格納するように最適化したのは、非常に一般的です。この最適化の欠点は、String
リテラルを変更するランタイムコードが、それを共有する他のすべてのコードのインスタンスを変更するため、問題が発生することです。たとえば、アプリケーションのどこかにある関数がString
リテラル"dog"
をに変更するのはよくありません"cat"
。A printf("dog")
はリテラルになります(つまり、不変にします)。一部のコンパイラー(OSからのサポート付き)は、"cat"
stdoutに書き込まれます。そのため、変更を試みるコードから保護する方法が必要でしたString
String
リテラルを特別な読み取り専用メモリセグメントに挿入すると、書き込みが行われた場合にメモリエラーが発生します。
Javaではこれはインターニングとして知られています。ここでのJavaコンパイラーは、何十年もの間コンパイラーによって行われた標準的なメモリー最適化に続いています。またString
、実行時に変更されるこれらのリテラルの同じ問題に対処するために、JavaはString
クラスを不変にするだけです(つまり、String
コンテンツを変更できるセッターを提供しません)。リテラルのString
内部処理がString
発生しなかった場合、sは不変である必要はありません。
String
てStringBuffer
いますが、残念ながら他のタイプはそのモデルに従いません。
1つの要因は、String
sが変更可能な場合、sを格納するオブジェクトはString
、内部データが予告なく変更されないように、コピーを格納するように注意する必要があるということです。String
sは数値のようにかなりプリミティブな型であることを考えると、たとえ参照によって渡されたとしても、値によって渡されたかのように扱うことができると便利です(これもメモリの節約に役立ちます)。
これがバンプであることは知っていますが、...本当に不変ですか 以下を検討してください。
public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
...
string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3
拡張メソッドにすることもできます。
public static class Extensions
{
public static unsafe void MutableReplaceIndex(this string s, char c, int i)
{
fixed (char* ptr = s)
{
*((char*)(ptr + i)) = c;
}
}
}
これは次の作業を行います
s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);
結論:コンパイラーが認識している不変の状態です。ただし、Javaにはポインタがないため、上記は.NET文字列にのみ適用されます。ただし、C#のポインターを使用して、文字列を完全に変更可能にすることができます。これは、ポインタの使用方法、実用的な使用方法、または安全に使用される方法ではありません。しかし、それは可能であり、したがって「可変」ルール全体を曲げます。通常、文字列のインデックスを直接変更することはできません。これが唯一の方法です。これは、文字列のポインターインスタンスを禁止するか、文字列がポイントされたときにコピーを作成することで防ぐことができますが、どちらも行われず、C#の文字列が完全に不変ではありません。
ほとんどの目的で、「文字列」は、数値のように、意味のある原子単位 (使用/処理/思考/仮定)ですです。
理由を知っておくべきです。考えてみてください。
私はそれを言うのが嫌いですが、残念ながら、私たちの言語はひどいので、私たちはこれについて議論しています、そして私たちは単一の単語、文字列を使用しようとしています、状況に応じた複雑な概念またはオブジェクトのクラスを記述ます。
数値の場合と同様に、「文字列」を使用して計算と比較を行います。文字列(または整数)が可変である場合、あらゆる種類の計算を確実に実行するために、それらの値を不変のローカルフォームにロックする特別なコードを記述する必要があります。したがって、文字列は数値識別子のように考えるのが最善ですが、16、32、または64ビット長ではなく、数百ビットの長さになる可能性があります。
誰かが「ひも」と言うとき、私たちはみんな違うことを考えます。特に目的を問わず、単なるキャラクターの集まりだと思っている人は、当然のことながら、キャラクターを操作できないように決めただけでびっくりします。しかし、 "string"クラスは単なる文字の配列ではありません。それはSTRING
ではなくchar[]
です。「文字列」と呼ぶ概念にはいくつかの基本的な前提があり、一般に、数値などのコード化されたデータの意味のある原子単位として説明できます。人々は「文字列操作」について話すとき、おそらく彼らは本当に操作の話をしている文字をビルドする文字列、およびStringBuilderのは、そのための素晴らしいです。
文字列が変更可能であるとしたら、それがどのようなものか少し考えてください。次のAPI関数は、変更可能なユーザー名の文字列が、この関数が使用している間に別のスレッドによって意図的または非意図的に変更された場合、別のユーザーの情報を返すようにだまされる可能性があります。
string GetPersonalInfo( string username, string password )
{
string stored_password = DBQuery.GetPasswordFor( username );
if (password == stored_password)
{
//another thread modifies the mutable 'username' string
return DBQuery.GetPersonalInfoFor( username );
}
}
セキュリティは、「アクセス制御」だけでなく、「安全性」と「正確性の保証」にも関係しています。メソッドを簡単に記述できず、単純な計算または比較を確実に実行するために依存できない場合、そのメソッドを呼び出すのは安全ではありませんが、プログラミング言語自体に問題を呼び出すのは安全です。
unsafe
またはを使用して)、または単にリフレクションによって変更できます(基になるフィールドを簡単に取得できます)。これは、意図的に文字列を変更したい人なら誰でも簡単に変更できるため、セキュリティ上のポイントを無効にします。ただし、これはプログラマにセキュリティを提供します。特別なことをしない限り、文字列は不変であることが保証されます(ただし、スレッドセーフではありません)。
C ++で文字列を変更可能にするという決定は多くの問題を引き起こします。KelvinHenneyによるMad COW Diseaseに関するこの優れた記事を参照してください。
COW =書き込み時のコピー。
それはトレードオフです。String
s String
プールに移動し、同じものを複数作成するとString
のsを、それらは同じメモリを共有します。プログラムは同じ文字列を何度もグラインドする傾向があるため、デザイナーはこのメモリ節約手法が一般的なケースでうまく機能すると考えました。
欠点は、連結によって多くの余分なが作成され、それが一時的なものにString
なり、ガベージになり、実際にメモリのパフォーマンスが低下することです。あなたは持っているStringBuffer
とStringBuilder
(Javaで、StringBuilder
これらのケースでメモリを維持するために使用する.NETでもあります)。
String
Javaのsは真に不変ではありません。リフレクションやクラスローディングを使用して、それらの値を変更できます。セキュリティのためにそのプロパティに依存するべきではありません。例については、JavaのMagic Trickを参照してください。
不変性は良好です。効果的なJavaを参照してください。文字列を渡すたびに文字列をコピーしなければならない場合は、エラーが発生しやすいコードになります。また、どの変更がどの参照に影響するかについても混乱しています。Integerがintのように動作するために不変でなければならないのと同じように、Stringはプリミティブのように動作するために不変として動作する必要があります。C ++では、文字列を値で渡すと、ソースコードで明示的に言及することなくこれが行われます。
ほとんどすべてのルールに例外があります。
using System;
using System.Runtime.InteropServices;
namespace Guess
{
class Program
{
static void Main(string[] args)
{
const string str = "ABC";
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
var handle = GCHandle.Alloc(str, GCHandleType.Pinned);
try
{
Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');
Console.WriteLine(str);
Console.WriteLine(str.GetHashCode());
}
finally
{
handle.Free();
}
}
}
}
これは主にセキュリティ上の理由によるものです。String
が改ざん防止されていると信頼できない場合は、システムを保護するのがはるかに難しくなります。