web-dev-qa-db-ja.com

Javaおよび.NETで文字列を変更できないのはなぜですか?

なぜ、彼らはJavaおよび.NET(および他のいくつかの言語)で文字列を不変にすることに決めたのですか?なぜそれを可変にしないのですか?

185
chrissie1

Effective Java 、第4章、73ページ、第2版によると:

「これには多くの正当な理由があります。不変クラスは、可変クラスよりも設計、実装、使用が簡単です。エラーが発生しにくく、安全です。

[...]

"不変オブジェクトは単純です。不変オブジェクトは、作成された状態と同じ状態になります。すべてのコンストラクタが確立することを確認した場合クラス不変式を使用すると、これらの不変式は常に努力されずに常に真であることが保証されます。

[...]

不変オブジェクトは本質的にスレッドセーフです。同期は必要ありません。複数のスレッドが同時にアクセスすることで破損することはありません。これは、スレッドセーフを実現するための最も簡単な方法です。実際、どのスレッドも、不変オブジェクトに対する別のスレッドの影響を監視できません。したがって、不変オブジェクトは自由に共有できます

[...]

同じ章の他の小さな点:

不変オブジェクトを共有できるだけでなく、その内部を共有できます。

[...]

不変オブジェクトは、可変であれ不変であれ、他のオブジェクトの優れた構成要素となります。

[...]

不変クラスの唯一の本当の欠点は、異なる値ごとに個別のオブジェクトが必要なことです。

202
PRINCESS FLUFF

少なくとも2つの理由があります。

最初の-securityhttp://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オブジェクトを指す多くの参照が存在します。誰かが値を変更すると、すべての参照に影響します。そこで、Sunはそれを不変にすることにしました。

102
Jorge Ferreira

実際、reason文字列はJavaはセキュリティとはあまり関係ありません。2つの主な理由は次のとおりです。

Theadの安全性:

文字列は非常に広く使用されているオブジェクトの種類です。したがって、マルチスレッド環境での使用がほぼ保証されます。文字列は不変であり、スレッド間で文字列を安全に共有できます。不変の文字列を使用すると、文字列をスレッドAから別のスレッドBに渡すときに、スレッドBがスレッドAの文字列を予期せず変更できなくなります。

これにより、すでにかなり複雑なマルチスレッドプログラミングのタスクが簡素化されるだけでなく、マルチスレッドアプリケーションのパフォーマンスも向上します。可変オブジェクトへのアクセスは、複数のスレッドからアクセスできる場合、何らかの方法で同期する必要があります。これにより、あるスレッドが別のスレッドによって変更されている間にオブジェクトの値を読み取ろうとしないようにします。適切な同期は、プログラマにとって正しく行うのが難しく、実行時に高価です。不変オブジェクトは変更できないため、同期する必要はありません。

性能:

文字列のインターンについて言及しましたが、これはJavaプログラムのメモリ効率のわずかな向上を表します。文字列リテラルのみがインターンされます。つまり、source codeは同じ文字列オブジェクトを共有します。プログラムが同じ文字列を動的に作成する場合、それらは異なるオブジェクトで表されます。

さらに重要なことに、不変の文字列を使用すると、内部データを共有できます。多くの文字列操作では、これは文字の基本配列をコピーする必要がないことを意味します。たとえば、文字列の最初の5文字を​​取得するとします。 Javaでは、myString.substring(0,5)を呼び出します。この場合、substring()メソッドは、myStringの基になるchar []を共有するが、そのchar []のインデックス0で始まりインデックス5で終わることを知っている新しいStringオブジェクトを作成するだけです。これをグラフィカルな形式にするには、次のようになります。

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

これにより、この種の操作は非常に安価になり、O(1)操作は元の文字列の長さにも抽出する必要のある部分文字列の長さにも依存しないためです。また、多くの文字列が基になるchar []を共有できるため、メモリの利点もあります。

57
LordOfThePigs

スレッドの安全性とパフォーマンス。文字列を変更できない場合、複数のスレッド間で参照を渡すことは安全かつ迅速です。文字列が変更可能な場合、文字列のすべてのバイトを新しいインスタンスに常にコピーするか、同期を提供する必要があります。典型的なアプリケーションは、文字列を変更する必要があるたびに文字列を100回読み取ります。 不変性 のウィキペディアを参照してください。

28
Matt Howells

「なぜXを変更可能にすべきなのか」と尋ねるべきです。 Princess Fluff で既に述べた利点があるため、デフォルトで不変性を使用することをお勧めします。何かが可変であることは例外であるべきです。

残念ながら、現在のプログラミング言語のほとんどはデフォルトで可変性ですが、将来的にはデフォルトがより不変性に近いことを願っています( 次のメインストリームプログラミング言語のウィッシュリスト を参照)。

11
Esko Luontola

文字列はプリミティブ型ではありませんが、通常、値のセマンティクス、つまり値のように使用します。

値は、あなたの背中の後ろで変化しないと信頼できるものです。次のように書いた場合:String str = someExpr(); strで何かをしなければ、変更したくないでしょう。

ObjectとしてのStringは、当然、ポインターセマンティクスを持ちます。値セマンティクスを取得するには、不変である必要があります。

7
David Pierre

うわー!ここでの誤報は信じられません。不変の文字列にはセキュリティがありません。実行中のアプリケーションのオブジェクトに誰かが既にアクセスしている場合(アプリ内でStringを「ハッキング」しようとする場合に想定する必要があります)、彼らは確かにハッキングに利用できる他の多くの機会になります。

Stringの不変性がスレッド化の問題に対処しているというのは、まったく新しい考えです。うーん... 2つの異なるスレッドによって変更されているオブジェクトがあります。どうすれば解決できますか?オブジェクトへのアクセスを同期しますか? Naawww ...誰にもオブジェクトを変更させないようにしましょう。これにより、厄介な並行性の問題がすべて修正されます。実際、すべてのオブジェクトを不変にしましょう。その後、Java言語から同期化された構文を削除できます。

本当の理由(上記の他の人によって指摘された)は、メモリの最適化です。同じ文字列リテラルを繰り返し使用することは、どのアプリケーションでも非常に一般的です。実際、非常に一般的であるため、何十年も前に、多くのコンパイラが文字列リテラルの単一インスタンスのみを格納する最適化を行いました。この最適化の欠点は、文字列リテラルを変更するランタイムコードが、それを共有する他のすべてのコードのインスタンスを変更するため、問題が発生することです。たとえば、アプリケーションのどこかにある関数が文字列リテラル「dog」を「cat」に変更するのは良くありません。 printf( "dog")を使用すると、 "cat"がstdoutに書き込まれます。そのため、文字列リテラルを変更しようとする(つまり、不変にする)コードから保護する方法が必要でした。一部のコンパイラ(OSのサポート付き)は、文字列リテラルを特別な読み取り専用メモリセグメントに配置することでこれを実現します。これにより、書き込みが試行された場合にメモリエラーが発生します。

In Javaこれはインターンとして知られています。ここでのJavaコンパイラは、何十年もコンパイラによって行われた標準的なメモリ最適化に従っています。実行時に変更されるこれらの文字列リテラル、Javaは、単にStringクラスを不変にします(つまり、文字列の内容を変更できるセッターを提供しません)。文字列は、文字列リテラルのインターンが発生しなかった場合は不変です。

7
Jim Barton

1つの要因は、文字列が可変である場合、文字列を格納するオブジェクトは、内部データが予告なしに変更されないように、コピーの格納に注意する必要があることです。文字列は数値のような非常に原始的なタイプであるため、参照渡しされたとしても、値渡しされたかのように扱うことができるのは素晴らしいことです(メモリの節約にも役立ちます)。

7
Evan DiBiase

私はこれがバンプであることを知っていますが、...それらは本当に不変ですか?以下を考慮してください。

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);

結論:それらはコンパイラーによって知られている不変の状態にあります。当然、上記は.NET文字列にのみ適用されます。Javaにはポインタがありません。ただし、文字列はC#のポインタを使用して完全に変更可能です。実用的な使用法または安全に使用されますが、それでも可能です。したがって、「可変」ルール全体を曲げます。通常、文字列のインデックスを直接変更することはできません。これが唯一の方法です。文字列のインスタンス、または文字列がポイントされたときにコピーを作成しますが、どちらも実行されないため、C#の文字列は完全に不変ではありません。

6
Bauss

ほとんどの場合、「文字列」は、(使用/処理/考え/想定される)意味のある原子単位数字のように

したがって、文字列の個々の文字が可変ではない理由を尋ねることは、整数の個々のビットが可変でない理由を尋ねるようなものです。

理由を知っておく必要があります。考えてみてください。

私はそれを言うのは嫌いですが、残念ながら私たちは言語が悪いのでこれを議論しており、単一の単語string、複雑で文脈的に位置する概念またはオブジェクトのクラスを記述します。

数値の場合と同様に、「文字列」で計算と比較を実行します。文字列(または整数)が可変の場合、あらゆる種類の計算を確実に実行するために、値を不変のローカルフォームにロックする特別なコードを記述する必要があります。したがって、文字列を数値識別子のように考えるのが最善ですが、16、32、または64ビット長ではなく、数百ビット長になる可能性があります。

誰かが「ひも」と言うとき、私たちは皆異なることを考えます。単に特定の目的を念頭に置いて、単に文字のセットと考えている人は、もちろん、誰かが自分がすべきではないと決めたことにapp然とするでしょうそれらのキャラクターを操作することができます。しかし、「文字列」クラスは単なる文字の配列ではありません。 char[]ではなく、STRINGです。 「文字列」と呼ぶ概念にはいくつかの基本的な仮定があり、一般に、数値のようなコード化されたデータの意味のある原子単位として説明できます。人々が「文字列の操作」について話すとき、おそらく彼らは実際にcharactersを操作してstringsを構築することについて話しているのであり、そのためにStringBuilderが最適です。 Wordの「文字列」の真の意味について少し考えてみてください。

文字列が変更可能な場合は、どのようになるかを少し考えてください。 mutableユーザー名文字列がこの関数の使用中に別のスレッドによって意図的にまたは意図せずに変更された場合、次の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 );
    }
}

セキュリティは「アクセス制御」だけではなく、「安全性」と「正確性の保証」も重要です。メソッドを簡単に記述できず、単純な計算または比較を確実に実行するために依存できない場合、メソッドを呼び出すことは安全ではありませんが、プログラミング言語自体を呼び出すことは安全です。

3
Triynko

不変性はセキュリティとそれほど密接に結びついていません。そのために、少なくとも.NETでは、SecureStringクラスを取得します。

3
Andrei Rînea

Javaの文字列は真に不変ではありません。リフレクションやクラスローディングを使用して値を変更できます。セキュリティのためにそのプロパティに依存するべきではありません。例を参照してください: Javaのマジックトリック

2
user80494

それはトレードオフです。文字列は文字列プールに入り、複数の同一の文字列を作成すると、それらは同じメモリを共有します。設計者は、プログラムが同じ文字列を頻繁にグラインドする傾向があるため、このメモリ節約技術は一般的なケースでうまく機能すると考えました。

マイナス面は、連結によって過渡的なだけの余分な文字列が大量に作成され、実際にはメモリパフォーマンスが損なわれるということです。これらの場合にメモリを保持するために使用するStringBufferとStringBuilder(JavaではStringBuilderも.NETにあります)があります。

2
aaronroyer

C++で文字列を変更可能にするという決定は多くの問題を引き起こします。 Mad COW Disease についてのKelvin Henneyによるこの素晴らしい記事を参照してください。

COW =書き込み時にコピー。

2
Motti

不変性は良好です。効果的な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();
            }
        }
    }
}
0
Lu4