これはこれまでで最も厄介な質問かもしれませんが、私はそれがJava初心者にとってはかなり混乱していると思います。
String
は不変なのはなぜですか?StringBuilder
のような変更可能なオブジェクトがStringやその逆よりも好まれるべきなのでしょうか?(Javaでの)いい例は本当に感謝されるでしょう。
不変は、いったんオブジェクトのコンストラクタが実行を完了すると、そのインスタンスは変更できないことを意味します。
これは、他の誰かがその内容を変更することを心配することなく、周りのオブジェクトへの参照を渡すことができることを意味するので便利です。 特に同時実行性を扱う場合、決して変更されないオブジェクトについてはロックの問題はありません
例えば.
class Foo
{
private final String myvar;
public Foo(final String initialValue)
{
this.myvar = initialValue;
}
public String getValue()
{
return this.myvar;
}
}
Foo
name__は、getValue()
の呼び出し元が文字列内のテキストを変更する可能性があることを心配する必要はありません。
Foo
name__ではなく、StringBuilder
name__をメンバーとして使用した、String
name__に似たクラスを想像すると、getValue()
への呼び出し元がStringBuilder
name__インスタンスのFoo
name__属性を変更できることがわかります。
また、さまざまな種類の不変性に気をつけてください。Eric Lippertは、これについて ブログ記事 を書きました。基本的に、インターフェースが不変であるが実際には可変のプライベート状態を背後に持っている(そしてスレッド間で安全に共有できない)オブジェクトを持つことができます。
不変オブジェクトは、内部フィールド(または少なくともその外部の動作に影響を与えるすべての内部フィールド)を変更できないオブジェクトです。
不変文字列には多くの利点があります。
Performance:以下の操作を行ってください。
String substring = fullstring.substring(x,y);
Substring()メソッドの基礎となるCは、おそらく次のようなものです。
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
どの文字もコピーする必要がないことに注意してください!Stringオブジェクトが変更可能な場合(文字が後で変更される可能性がある場合)、すべての文字をコピーする必要があります。そうでなければ、部分文字列内の文字への変更は、後で他の文字列に反映されます。
Concurrency:不変オブジェクトの内部構造が有効であれば、それは常に有効です。異なるスレッドがそのオブジェクト内に無効な状態を作成する可能性はありません。したがって、不変オブジェクトはスレッドセーフです。
ガベージコレクション:ガベージコレクタは、不変オブジェクトについて論理的な決定を下すのがはるかに簡単です。
しかし、不変性にも欠点があります。
Performance:ちょっと待って、パフォーマンスは不変性の逆だと言ったのですが。まあ、それは時々ありますが、いつもではありません。次のコードを見てください。
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
2行とも4番目の文字を "a"に置き換えます。 2番目のコードは読みやすくなっただけでなく、速くなりました。 fooの基礎となるコードをどのようにしなければならないのかを見てください。部分文字列は簡単ですが、スペース5にはすでに文字があり、それ以外のものがfooを参照している可能性があるため、変更することはできません。文字列全体をコピーする必要があります(もちろん、この機能の一部は実際のCの基礎となる関数に抽象化されていますが、ここで重要なのは、すべての場所で実行されるコードを示すことです)。
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
連結は2回と呼ばれるので、文字列全体をループスルーする必要があることに注意してください。これをbar
操作のCコードと比較してください。
bar->characters[4] = 'a';
可変文字列操作は明らかにはるかに高速です。
結論としてほとんどの場合、不変の文字列が必要です。しかし、もしあなたが文字列にたくさんの追加や挿入をする必要があるなら、スピードのための可変性が必要です。同時実行性の安全性とガベージコレクションの利点を得たい場合は、可変オブジェクトをメソッドに対してローカルに保つことが重要です。
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
mutable
オブジェクトはローカル参照なので、同時実行の安全性について心配する必要はありません(1つのスレッドだけがこれに触れることはありません)。また、他の場所で参照されていないため、スタック上でのみ割り当てられるため、関数呼び出しが終了するとすぐに割り当て解除されます(ガベージコレクションを心配する必要はありません)。そして、あなたは可変性と不変性の両方のすべてのパフォーマンス上の利点を得ます。
あなたが上で提案されたウィキペディアの定義を使用する場合、実際にはStringは不変ではありません。
文字列の状態はポストコンストラクションを変更します。 hashcode()メソッドを見てください。 Stringはハッシュコード値をローカルフィールドにキャッシュしますが、hashcode()が最初に呼び出されるまでそれを計算しません。ハッシュコードのこの怠惰な評価は、状態が変化する不変オブジェクトとしてStringを興味深い位置に配置しますが、リフレクションを使用せずに変化したことは観察できません。
そのため、不変の定義は、変更されたことが観察できないオブジェクトになるはずです。
不変オブジェクトが作成された後にその状態が不変オブジェクト内で変化したが、それを(反射なしで)誰も見ることができない場合、そのオブジェクトはまだ不変です。
不変オブジェクトとは、プログラムで変更できないオブジェクトです。特に、マルチスレッド環境や、複数のプロセスがオブジェクトの値を変更(変更)できる他の環境に適しています。
ただし、明確にするために、StringBuilderは実際には可変オブジェクトであり、不変オブジェクトではありません。通常のJava文字列は不変です(一度作成すると、オブジェクトを変更せずに基になる文字列を変更することはできません)。
たとえば、文字列の値と文字列の色を持つColoredStringというクラスがあるとします。
public class ColoredString {
private String color;
private String string;
public ColoredString(String color, String string) {
this.color = color;
this.string = string;
}
public String getColor() { return this.color; }
public String getString() { return this.string; }
public void setColor(String newColor) {
this.color = newColor;
}
}
この例では、新しいColoredStringクラスを作成せずにキープロパティの1つを変更(変更)できるため、ColoredStringは可変であると言われます。これが悪い理由は、たとえば、複数のスレッドを持つGUIアプリケーションがあり、ColoredStringsを使用してウィンドウにデータを印刷しているとします。として作成されたColoredStringのインスタンスがある場合
new ColoredString("Blue", "This is a blue string!");
その場合、文字列は常に「青」になると予想されます。ただし、別のスレッドがこのインスタンスを取得して呼び出された場合
blueString.setColor("Red");
「青い」文字列が必要なときに、突然、そしておそらく予期せずに、「赤い」文字列になります。このため、オブジェクトのインスタンスを渡す場合、ほとんどの場合、不変オブジェクトが優先されます。可変オブジェクトが本当に必要な場合は、通常、特定の制御フィールドからコピーを渡すだけでオブジェクトを保護します。
要約すると、Javaでは、Java.lang.Stringは不変オブジェクトであり(作成後はcannot変更できません)、Java.lang.StringBuilderは新しいインスタンスを作成せずに変更できるため、可変オブジェクト。
文字列s1 = "古い文字列";
//s1 variable, refers to string in memory
reference | MEMORY |
variables | |
[s1] --------------->| "Old String" |
文字列s2 = s1;
//s2 refers to same string as s1
| |
[s1] --------------->| "Old String" |
[s2] ------------------------^
s1 = "新しい文字列";
//s1 deletes reference to old string and points to the newly created one
[s1] -----|--------->| "New String" |
| | |
|~~~~~~~~~X| "Old String" |
[s2] ------------------------^
元の文字列 'in memory'は変更されませんでしたが、参照変数は新しい文字列を参照するように変更されました。そして、もし私たちがs2を持っていなければ、 "Old String"はまだメモリ内にあるでしょうが、それにアクセスすることはできないでしょう...
「不変」とは、値を変更できないことを意味します。 Stringクラスのインスタンスがある場合、値を変更するように思われるメソッドを呼び出すと、実際には別のStringが作成されます。
String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"
変更を保存するには、次のようにします。foo = foo.sustring(3);
イミュータブルとミュータブルは、コレクションを扱うときには面白いことがあります。可変オブジェクトをmapのキーとして使用してから値を変更するとどうなるかを考えます(ヒント:equals
とhashCode
について考えます)。
少し遅れるかもしれませんが、不変オブジェクトとは何かを理解するために、新しいJava 8 Date and Time API( Java.time )の次の例を検討してください。ご存じのとおり、Java 8の日付オブジェクトはすべて不変であるため、次の例のようになります。
LocalDate date = LocalDate.of(2014, 3, 18);
date.plusYears(2);
System.out.println(date);
出力:
2014-03-18
plusYears(2)
は新しいオブジェクトを返すので、これは最初の日付と同じ年を出力します。古い日付は不変のオブジェクトなので変更されません。いったん作成されると、それをそれ以上修正することはできず、date変数はまだそれを指しています。
そのため、そのコード例では、plusYears
への呼び出しによってインスタンス化され返された新しいオブジェクトを取得して使用する必要があります。
LocalDate date = LocalDate.of(2014, 3, 18);
LocalDate dateAfterTwoYears = date.plusYears(2);
date.toString()…2014-03-18
dateAfterTwoYears.toString()…2016-03-18
不変のオブジェクトは、作成後に状態を変更することはできません。
不変オブジェクトを使用できるときはいつでも使用する主な理由が3つあります。これらすべてが、コードに導入されるバグの数を減らすのに役立ちます。
オブジェクトの状態が不変であることがわかっている場合は、コード内で他にも最適化を行うことができます(たとえば、計算されたハッシュをキャッシュするなど)。しかし、これらは最適化であり、それほど面白くありません。
私は本当に SCJP Sun認定プログラマーのためのJava 5学習ガイド の説明を気に入っています。
Javaのメモリ効率を高めるために、JVMは「文字列定数プール」と呼ばれる特別なメモリ領域を確保しています。コンパイラが文字列リテラルを検出すると、プールを調べて、同じ文字列がすでに存在するかどうかを確認します。一致が見つかった場合、新しいリテラルへの参照は既存のStringに向けられ、新しいStringリテラルオブジェクトは作成されません。
1つの意味は、値がコンピュータにどのように格納されるかに関係しています。たとえば、.Net文字列の場合、メモリ内の文字列は変更できないことを意味します。メモリ内の文字列で、既存の変数(実際には他の場所にある実際の文字の集合へのポインタ)を新しい文字列にポイントします。
String s1="Hi";
String s2=s1;
s1="Bye";
System.out.println(s2); //Hi (if String was mutable output would be: Bye)
System.out.println(s1); //Bye
s1="Hi"
:オブジェクトs1
は "Hi"の値で作成されました。
s2=s1
:オブジェクトs2
は、s1オブジェクトを参照して作成されます。
s1="Bye"
:s1
はString型でString型は不変の型であるため、前のs1
オブジェクトの値は変わりません。代わりに、コンパイラは "Bye"値とそれを参照するs1
を持つ新しいStringオブジェクトを作成します。ここでs2
値を出力すると、s2
は「Hi」値を持つ前のs1
オブジェクトを参照していたので、結果は「Bye」ではなく「Hi」になります。
不変は、単に変更不可能または変更不可能を意味します。文字列オブジェクトが作成されたら、そのデータまたは状態は変更できません
次の例を見てください。
class Testimmutablestring{
public static void main(String args[]){
String s="Future";
s.concat(" World");//concat() method appends the string at the end
System.out.println(s);//will print Future because strings are immutable objects
}
}
下図を考えて考えましょう
この図では、「Future World」として作成された新しいオブジェクトを見ることができます。しかし、 "Future"を変更しないでください。Because String is immutable
。 s
、それでも「未来」を参照してください。あなたが「未来の世界」と呼ぶ必要があるならば、
String s="Future";
s=s.concat(" World");
System.out.println(s);//print Future World
文字列オブジェクトはなぜJavaでは不変なのですか?
Javaは文字列リテラルの概念を使用しているからです。 5つの参照変数があり、すべてが1つのオブジェクト "Future"を参照しているとします。1つの参照変数がオブジェクトの値を変更すると、すべての参照変数に影響します。文字列オブジェクトがJavaでは不変なのはそのためです。
不変とは、いったんオブジェクトが作成されると、そのメンバー以外は変更されないことを意味します。 String
は内容を変更できないため不変です。例えば:
String s1 = " abc ";
String s2 = s1.trim();
上記のコードでは、文字列s1は変更されず、s2
を使用して別のオブジェクト(s1
)が作成されました。
一度インスタンス化されると、変更することはできません。のインスタンスがハッシュテーブルなどのキーとして使用される可能性があるクラスを考えてみましょう。 Javaのベストプラクティスを調べてください。
オブジェクトが構築された後にその状態を変更できない場合、オブジェクトは不変不変と見なされます。不変オブジェクトへの最大の依存は、単純で信頼できるコードを作成するための適切な戦略として広く受け入れられています。
不変オブジェクトは、並行アプリケーションで特に役立ちます。それらは状態を変えることができないので、それらはスレッドの干渉によって破損したり、矛盾した状態で観察されたりすることはできません。
私は 投稿 からこのフレーズが好きです
不変オブジェクトは並行処理プログラミングを容易にする
不変オブジェクト
オブジェクトは、構築後にその状態を変更できない場合、不変と見なされます。不変オブジェクトへの最大の依存は、単純で信頼できるコードを作成するための適切な戦略として広く受け入れられています。
不変オブジェクトは、並行アプリケーションで特に役立ちます。それらは状態を変えることができないので、それらはスレッドの干渉によって破損したり、矛盾した状態で観察されたりすることはできません。
プログラマは、不変のオブジェクトを採用することに消極的です。オブジェクトを所定の位置に更新するのではなく、新しいオブジェクトを作成するコストを心配するからです。オブジェクト作成の影響はしばしば過大評価されており、不変オブジェクトに関連するいくつかの効率によって相殺される可能性があります。これには、ガベージコレクションによるオーバーヘッドの減少、および可変オブジェクトを破損から保護するために必要なコードの削除が含まれます。
以下のサブセクションでは、インスタンスが変更可能なクラスを取り、それから不変のインスタンスを持つクラスを派生させます。そうすることで、彼らはこの種の変換のための一般的な規則を与え、不変オブジェクトの利点のいくつかを実証します。