String
彼らがJavaと.NET(および他のいくつかの言語)で不変にすることを決めたのはなぜですか?なぜ彼らはそれを可変にしなかったのですか?
17 に答える
Effective Javaによると、第 4 章、73 ページ、第 2 版:
「これには多くの正当な理由があります。不変クラスは、可変クラスよりも設計、実装、および使用が容易です。エラーが発生しにくく、より安全です。
[...]
"不変オブジェクトは単純です。不変オブジェクトは、それが作成されたときの状態とまったく同じ状態になることができます。すべてのコンストラクターがクラスの不変条件を確立することを確認すると、これらの不変条件が常に真であることが保証されます。あなたの努力は必要ありません。
[...]
不変オブジェクトは本質的にスレッドセーフです。同期は必要ありません。複数のスレッドが同時にアクセスしても破損することはありません。これは、スレッドセーフを達成するための最も簡単な方法です。実際、不変オブジェクトに対する別のスレッドの影響を観察できるスレッドはありません。したがって、 不変オブジェクトは自由に共有できます
[...]
同じ章のその他の小さなポイント:
不変オブジェクトを共有できるだけでなく、その内部も共有できます。
[...]
不変オブジェクトは、可変であるか不変であるかにかかわらず、他のオブジェクトの優れた構成要素になります。
[...]
不変クラスの唯一の実際の欠点は、個別の値ごとに個別のオブジェクトが必要になることです。
There are at least two reasons.
First - security http://www.javafaq.nu/java-article1060.html
The main reason why String made immutable was security. Look at this example: We have a file open method with login check. We pass a String to this method to process authentication which is necessary before the call will be passed to OS. If String was mutable it was possible somehow to modify its content after the authentication check before OS gets request from program then it is possible to request any file. So if you have a right to open text file in user directory but then on the fly when somehow you manage to change the file name you can request to open "passwd" file or any other. Then a file can be modified and it will be possible to login directly to OS.
Second - Memory efficiency http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html
JVM internally maintains the "String Pool". To achive the memory efficiency, JVM will refer the String object from pool. It will not create the new String objects. So, whenever you create a new string literal, JVM will check in the pool whether it already exists or not. If already present in the pool, just give the reference to the same object or create the new object in the pool. There will be many references point to the same String objects, if someone changes the value, it will affect all the references. So, sun decided to make it immutable.
実際、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[] を共有できるため、この動作にはメモリ上の利点もあります。
スレッドの安全性とパフォーマンス。文字列を変更できない場合は、複数のスレッド間で安全かつ迅速に参照を渡すことができます。文字列が変更可能な場合、常に文字列のすべてのバイトを新しいインスタンスにコピーするか、同期を提供する必要があります。通常のアプリケーションでは、文字列の変更が必要になるたびに、その文字列を 100 回読み取ります。不変性に関するウィキペディアを参照してください。
String
はプリミティブ型ではありませんが、通常は値のセマンティクス、つまり値のように使用します。
価値とは、あなたが信頼できるものであり、あなたの後ろで変わることはありません. あなたが書くなら:String str = someExpr();
あなたが何かをしない限り、あなたはそれを変えたくないstr
.
String
にObject
は当然ポインターのセマンティクスがあるため、値のセマンティクスも取得するには、不変である必要があります。
1 つの要因は、String
s がミュータブルである場合、s を格納するオブジェクトはString
、内部データが予告なしに変更されないように注意してコピーを格納する必要があることです。s が数値のようにかなりプリミティブな型であることを考えると、String
それらが参照によって渡されたとしても、値によって渡されたかのように扱うことができると便利です (これはメモリの節約にも役立ちます)。
私はこれが隆起であることを知っていますが.それらは本当に不変ですか? 以下を検討してください。
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# の文字列は完全には不変ではありません。
不変性はセキュリティとそれほど密接に関係していません。そのために、少なくとも.NETでは、SecureString
クラスを取得します。
後で編集:JavaにはGuardedString
、同様の実装があります。
ほとんどの場合、「文字列」は (使用/処理/考え/想定される) 意味のある原子単位で あり、数値と同様です。
したがって、文字列の個々の文字が可変でない理由を尋ねることは、整数の個々のビットが可変でない理由を尋ねるようなものです。
その理由を知っておく必要があります。考えてみてください。
言いたくないのですが、残念ながら、私たちの言語はひどいものであり、単一の単語stringを使用して、複雑で文脈的に位置付けられた概念またはオブジェクトのクラスを記述しようとしているため、これについて議論しています。
数値と同様に、「文字列」を使用して計算と比較を実行します。文字列 (または整数) が可変である場合、あらゆる種類の計算を確実に実行するために、それらの値を不変のローカル形式にロックする特別なコードを作成する必要があります。したがって、文字列を数値識別子のように考えるのが最善ですが、16、32、または 64 ビットの長さではなく、数百ビットの長さになる可能性があります。
誰かが「ひも」と言うとき、私たちは皆、さまざまなことを考えます。特定の目的を念頭に置いて単純に文字のセットと考えている人は、もちろん、誰かがそれらの文字を操作できないと判断したことに愕然とするでしょう. しかし、「文字列」クラスは単なる文字の配列ではありません。では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 );
}
}
セキュリティは「アクセス制御」だけではなく、「安全性」と「正確性の保証」でもあります。メソッドが簡単に記述できず、単純な計算や比較を確実に実行するために依存している場合、それを呼び出すのは安全ではありませんが、プログラミング言語自体に疑問を投げかけるのは安全です.
C++ で文字列をミュータブルにするという決定は、多くの問題を引き起こします。Kelvin Henney によるMad COW Diseaseに関する優れた記事 を参照してください。
COW = コピー オン ライト。
String
Java の s は本当に不変ではありません。リフレクションやクラスの読み込みを使用して値を変更できます。セキュリティのためにそのプロパティに依存するべきではありません。例については、次を参照してください: Java での Magic Trick
それはトレードオフです。はプールString
に入り、複数の同一の を作成すると、それらは同じメモリを共有します。設計者は、プログラムは同じ文字列を頻繁に処理する傾向があるため、このメモリ節約手法は一般的なケースでうまく機能すると考えました。String
String
欠点は、連結によって大量の余分なString
s が作成されることです。これらは一時的なものであり、ゴミになるだけであり、実際にはメモリ パフォーマンスが損なわれます。これらの場合にメモリを保持するために使用する必要がありStringBuffer
ますStringBuilder
(Java では、 .NET でもあります)。StringBuilder
ほぼすべてのルールに例外があります。
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();
}
}
}
}
不変性は良いです。効果的な Java を参照してください。String を渡すたびにコピーしなければならない場合、それは多くのエラーが発生しやすいコードになります。また、どの変更がどの参照に影響するかについても混乱します。整数が int のように振る舞うために不変でなければならないのと同じように、文字列はプリミティブのように振る舞うために不変として振る舞う必要があります。C++ では、値による文字列の受け渡しは、ソース コードで明示的に言及せずにこれを行います。
これは主にセキュリティ上の理由によるものです。String
システムが改ざん防止されていると信頼できない場合、システムを保護することははるかに困難です。