私は不変文字列について次のようなコードを書きました。
public class ImmutableStrings {
public static void main(String[] args) {
testmethod();
}
private static void testmethod() {
String a = "a";
System.out.println("a 1-->" + a);
a = "ty";
System.out.println("a 2-->" + a);
}
}
出力:
a 1-->a
a 2-->ty
ここで、変数a
の値が変更されました(一方、不変オブジェクトの内容は変更できないと多くの人が言っています)。しかし、String
が不変であるということは、どういう意味ですか?このトピックを私のために明確にしてください。
ソース: https://docs.Oracle.com/javase/tutorial/Java/nutsandbolts/datatypes.html
immutabilityの大騒ぎを進める前に、結論に至る少し前に、String
クラスとその機能を見てみましょう。
これはString
の仕組みです:
String str = "knowledge";
これは、通常どおり、"knowledge"
を含む文字列を作成し、参照str
を割り当てます。簡単ですか?さらにいくつかの機能を実行できます。
String s = str; // assigns a new reference to the same string "knowledge"
以下のステートメントの仕組みを見てみましょう。
str = str.concat(" base");
これは、str
に文字列" base"
を追加します。しかし、String
オブジェクトは不変なので、これはどのように可能ですか?驚いたことに、そうです。
上記のステートメントが実行されると、VMはString str
の値、つまり"knowledge"
を取り、" base"
を追加して、値"knowledge base"
を与えます。現在、String
sは不変であるため、VMはこの値をstr
に割り当てることができないため、新しいString
オブジェクトを作成し、値"knowledge base"
を与え、参照str
を与えます。
ここで注意すべき重要な点は、String
オブジェクトは不変ですが、その参照変数はそうではありませんです。そのため、上記の例では、新しく作成されたString
オブジェクトを参照するために参照が行われました。 。
上記の例のこの時点で、2つのString
オブジェクトがあります。最初のオブジェクトは、s
が指す"knowledge"
の値で作成し、2番目はstr
が指す"knowledge base"
です。しかし、技術的には、3つのString
オブジェクトがあり、3番目のオブジェクトはconcat
ステートメントのリテラル"base"
です。
"knowledge"
への別の参照s
がない場合はどうなりますか?そのString
は失われていたでしょう。ただし、それはまだ存在していましたが、参照がないために失われたと見なされます。以下のもう1つの例を見てください
String s1 = "Java";
s1.concat(" rules");
System.out.println("s1 refers to "+s1); // Yes, s1 still refers to "Java"
何が起きているのか:
String
"Java"
を作成し、s1
を参照します。String
"Java rules"
を作成しますが、それを参照するものはありません。したがって、2番目のString
はすぐに失われます。届かない。参照変数s1
は、元のString
"Java"
を引き続き参照しています。
変更するためにString
オブジェクトに適用されるほぼすべてのメソッドは、新しいString
オブジェクトを作成します。では、これらのString
オブジェクトはどこに行くのでしょうか?さて、これらはメモリに存在し、プログラミング言語の重要な目標の1つはメモリを効率的に使用することです。
アプリケーションが成長するにつれて、String
リテラルがメモリの大きな領域を占有することは非常に一般的であり、冗長性を引き起こすことさえあります。したがって、Javaより効率的、JVMは「文字列定数プール」と呼ばれる特別なメモリ領域を確保します。
コンパイラがString
リテラルを検出すると、プールでString
を探します。一致が見つかった場合、新しいリテラルへの参照は既存のString
に向けられ、新しいString
オブジェクトは作成されません。既存のString
には、もう1つの参照があります。 String
オブジェクトを不変にするポイントがここにあります:
String
定数プールでは、String
オブジェクトに1つ以上の参照が含まれている可能性があります。 複数の参照がそれを知らなくても同じString
を指している場合、参照の1つがそのString
値を変更した場合、それは悪いでしょう。 String
オブジェクトが不変である理由です。
さて、あなたは言うことができます、誰かがString
クラスの機能をオーバーライドするとどうなりますか?それがString
クラスがfinal
とマークされている理由です誰もそのメソッドの動作をオーバーライドできないようにします。
Stringは不変であるということは、オブジェクト自体を変更することはできませんが、オブジェクトへの参照を変更することはできます。
a = "ty"
を実行すると、実際にはa
の参照を文字列リテラル"ty"
によって作成された新しいオブジェクトに変更します。
オブジェクトを変更するとは、そのメソッドを使用してそのフィールドの1つを変更することを意味します(またはフィールドはパブリックで最終的ではないため、メソッドからアクセスせずに外部から更新できます)。
Foo x = new Foo("the field");
x.setField("a new field");
System.out.println(x.getField()); // prints "a new field"
Stringなど、不変クラス(継承による変更を防ぐためにfinalとして宣言されている)(そのメソッドはそのフィールドを変更できず、フィールドは常に非公開で、finalにすることをお勧めします)では、現在のStringは変更できませんが新しい文字列を返すことができます、すなわち:
String s = "some text";
s.substring(0,4);
System.out.println(s); // still printing "some text"
String a = s.substring(0,4);
System.out.println(a); // prints "some"
a
の参照先を変更しています。これを試して:
String a="a";
System.out.println("a 1-->"+a);
String b=a;
a="ty";
System.out.println("a 2-->"+a);
System.out.println("b -->"+b);
a
そして次にb
が参照するオブジェクトは変更されていないことがわかります。
コードがa
が参照するオブジェクトを変更しないようにしたい場合は、次のことを試してください。
final String a="a";
文字列は、一連の TF-16コード単位 、その配列へのint
オフセット、およびint
長さを含むchar[]
です。
例えば。
String s
文字列参照用のスペースを作成します。参照を割り当てると、参照がコピーされますが、それらの参照が参照するオブジェクトは変更されません。
また、それに注意する必要があります
new String(s)
本当に便利なことは何もしません。 s
と同じ配列、オフセット、長さでバッキングされた別のインスタンスを作成するだけです。これを行う理由はほとんどないため、ほとんどのJavaプログラマーにとっては悪い習慣と見なされます。
"my string"
のようなJavaの二重引用符付き文字列は、実際には internedString
インスタンスへの参照です。したがって、"bar"
は、出現回数に関係なく同じStringインスタンスへの参照です。あなたのコード。
「hello」はプールされる1つのインスタンスを作成し、new String(...)
はプールされないインスタンスを作成します。 System.out.println(("hello" == "hello") + "," + (new String("hello") == "hello") + "," + (new String("hello") == new String("hello")));
を試してください。true,false,false
が表示されます
不変とは、同じreferanceの値を変更できないことを意味します。新しいreferanceを作成するのに必要な時間が新しいメモリの場所を意味します。例:
String str="abc";
str="bcd";
ここで、上記のコードでは、メモリ内に値を記憶するための2つのブロックがある。第1の値は「abc」、第2の値は「bcd」であり、第2の値は第1の値に置き換えられない。
これは不変と呼ばれます。
こちらをご覧ください
class ImmutableStrings {
public static void main(String[] args) {
testmethod();
}
private static void testmethod() {
String a="a";
System.out.println("a 1-->"+a);
System.out.println("a 1 address-->"+a.hashCode());
a = "ty";
System.out.println("a 2-->"+a);
System.out.println("a 2 address-->"+a.hashCode());
}
}
出力:
a 1-->a
a 1 address-->97
a 2-->ty
a 2 address-->3717
これは、不変の文字列オブジェクトa
の内容を変更しているときはいつでも新しいオブジェクトが作成されることを示します。つまり、不変オブジェクトの内容を変更することは許可されていません。両方のオブジェクトでアドレスが異なるのはそのためです。
あなたの例では、変数a
は単に文字列オブジェクトのインスタンスへの参照です。 a = "ty"
と言っても、実際には文字列オブジェクトを変更するのではなく、文字列クラスのまったく異なるインスタンスを参照するように指定します。
String S1="abc";
S1.concat("xyz");
System.out.println("S1 is", + S1);
String S2=S1.concat("def");
System.out.println("S2 is", + S2);
これは、文字列オブジェクトがいったん作成されると変更できないことを示しています。毎回、新しく作成して別の文字列を入れる必要があります。 S
不変オブジェクトは、作成後に状態を変更できないオブジェクトです。
だからa = "ABC"
< - 不変オブジェクト。 "a"はオブジェクトへの参照を保持します。そして、a = "DEF"
< - もう一つの不変オブジェクト、 "a"は今それを参照しています。
文字列オブジェクトを割り当てた後は、そのオブジェクトをメモリ内で変更することはできません。
まとめると、 "a"の参照を新しい文字列オブジェクトに変更することです。
JavaのString
は不変で、String
は値をオブジェクトの形式で格納します。したがって、uに値String a="a";
を代入するとオブジェクトが作成され、値がその中に格納されます。値a="ty"
を代入している場合には、別のオブジェクトがその中に値を格納します。明確に理解したいのであれば、String
のhas code
をチェックしてください。
次のコードで違いが解消されると思います。
String A = new String("Venugopal");
String B = A;
A = A +"mitul";
System.out.println("A is " + A);
System.out.println("B is " + B);
StringBuffer SA = new StringBuffer("Venugopal");
StringBuffer SB = SA;
SA = SA.append("mitul");
System.out.println("SA is " + SA);
System.out.println("SB is " + SB);
代入文でオブジェクトを変更しているのではなく、replaceを1つの不変オブジェクトに置き換えます。オブジェクトString("a")
はString("ty")
に変更されずに破棄され、代わりにreferenceからty
が代わりにa
に書き込まれます。
対照的に、StringBuffer
は、mutableオブジェクトを表します。あなたはこれを行うことができます:
StringBuffer b = new StringBuffer("Hello");
System.out.writeln(b);
b.append(", world!");
System.out.writeln(b);
ここでは、b
を再割り当てしませんでした。それはまだ同じオブジェクトを指していますが、そのオブジェクトのcontentは変更されています。
Stringは不変です。つまり、Stringオブジェクトの内容は、一度作成されると変更できません。内容を変更したい場合は、StringではなくStringBuffer/StringBuilderを選択してください。 StringBufferとStringBuilderは可変クラスです。
以下のコードがあなたの疑問を明確にすることを願っています:
public static void testString() {
String str = "Hello";
System.out.println("Before String Concat: "+str);
str.concat("World");
System.out.println("After String Concat: "+str);
StringBuffer sb = new StringBuffer("Hello");
System.out.println("Before StringBuffer Append: "+sb);
sb.append("World");
System.out.println("After StringBuffer Append: "+sb);
}
文字列Concatの前:こんにちは
String Concatの後:こんにちは
StringBufferの前に追加:こんにちは
StringBufferの後追加:HelloWorld
おそらく上記のすべての答えは正しいのですが、私の答えはhashCode()
メソッドの使用に固有のもので、String ...などのポイントを証明するために変更することはできず、変更すると異なるメモリ位置で新しい値になります。
public class ImmutabilityTest {
private String changingRef = "TEST_STRING";
public static void main(String a[]) {
ImmutabilityTest dn = new ImmutabilityTest();
System.out.println("ChangingRef for TEST_STRING OLD : "
+ dn.changingRef.hashCode());
dn.changingRef = "NEW_TEST_STRING";
System.out.println("ChangingRef for NEW_TEST_STRING : "
+ dn.changingRef.hashCode());
dn.changingRef = "TEST_STRING";
System.out.println("ChangingRef for TEST_STRING BACK : "
+ dn.changingRef.hashCode());
dn.changingRef = "NEW_TEST_STRING";
System.out.println("ChangingRef for NEW_TEST_STRING BACK : "
+ dn.changingRef.hashCode());
String str = new String("STRING1");
System.out.println("String Class STRING1 : " + str.hashCode());
str = new String("STRING2");
System.out.println("String Class STRING2 : " + str.hashCode());
str = new String("STRING1");
System.out.println("String Class STRING1 BACK : " + str.hashCode());
str = new String("STRING2");
System.out.println("String Class STRING2 BACK : " + str.hashCode());
}
}
ChangingRef for TEST_STRING OLD : 247540830
ChangingRef for NEW_TEST_STRING : 970356767
ChangingRef for TEST_STRING BACK : 247540830
ChangingRef for NEW_TEST_STRING BACK : 970356767
String Class STRING1 : -1163776448
String Class STRING2 : -1163776447
String Class STRING1 BACK : -1163776448
String Class STRING2 BACK : -1163776447
一部のオブジェクトbar
が可変オブジェクトfoo
への参照を保持し、その状態の一部をfoo
の状態の可変部分にカプセル化する場合、foo
のこれらの側面を変更できるコードは、bar
に実際に触れることなく、bar
の状態の対応する側面を変更できます。その存在さえ知っている。一般的に、これは、可変オブジェクトを使用して自身の状態をカプセル化するオブジェクトは、それらのオブジェクトへの参照が予期せず変更される可能性のあるコードにさらされないことを保証する必要があることを意味します。対照的に、bar
がオブジェクトmoo
への参照を保持し、moo
の恒等性以外の不変の側面のみを使用する場合その状態をカプセル化するために、bar
はmoo
を外部コードに自由に公開できます。
参照だけが変化しています。最初のa
は文字列 "a"を参照していましたが、後であなたはそれを "ty"に変更しました。文字列 "a"は変わりません。
あなたの例では、a
は最初に"a"
を参照し、次に"ty"
を参照します。あなたはString
インスタンスを変更していません。 String
が参照するa
インスタンスを変更するだけです。たとえば、
String a = "a";
String b = a; // b refers to the same String as a
a = "b"; // a now refers to a different instance
System.out.println(b);
String
が指すb
インスタンスは変更されないので、 "a"を出力します。