web-dev-qa-db-ja.com

文字列は不変です。正確にはどういう意味ですか?

私は不変文字列について次のようなコードを書きました。

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

185
Anuj Balan

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"を与えます。現在、Stringsは不変であるため、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"

何が起きているのか:

  1. 最初の行は非常に簡単です。新しいString"Java"を作成し、s1を参照します。
  2. 次に、VMは別の新しい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とマークされている理由です誰もそのメソッドの動作をオーバーライドできないようにします。

388
roger_that

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"
170
Eng.Fouad

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";
18
Greg Hewgill

文字列は、一連の 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が表示されます

5
Lion

不変とは、同じreferanceの値を変更できないことを意味します。新しいreferanceを作成するのに必要な時間が新しいメモリの場所を意味します。例:

String str="abc";
str="bcd";

ここで、上記のコードでは、メモリ内に値を記憶するための2つのブロックがある。第1の値は「abc」、第2の値は「bcd」であり、第2の値は第1の値に置き換えられない。

これは不変と呼ばれます。

4
Rakesh Patel

こちらをご覧ください

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の内容を変更しているときはいつでも新しいオブジェクトが作成されることを示します。つまり、不変オブジェクトの内容を変更することは許可されていません。両方のオブジェクトでアドレスが異なるのはそのためです。

3
Rustam

あなたの例では、変数aは単に文字列オブジェクトのインスタンスへの参照です。 a = "ty"と言っても、実際には文字列オブジェクトを変更するのではなく、文字列クラスのまったく異なるインスタンスを参照するように指定します。

3
Jon Newmuis
String S1="abc";
S1.concat("xyz");
System.out.println("S1 is", + S1);
String S2=S1.concat("def");
System.out.println("S2 is", + S2);

これは、文字列オブジェクトがいったん作成されると変更できないことを示しています。毎回、新しく作成して別の文字列を入れる必要があります。 S

2
Surya

不変オブジェクトは、作成後に状態を変更できないオブジェクトです。

だからa = "ABC" < - 不変オブジェクト。 "a"はオブジェクトへの参照を保持します。そして、a = "DEF" < - もう一つの不変オブジェクト、 "a"は今それを参照しています。

文字列オブジェクトを割り当てた後は、そのオブジェクトをメモリ内で変更することはできません。

まとめると、 "a"の参照を新しい文字列オブジェクトに変更することです。

JavaのStringは不変で、Stringは値をオブジェクトの形式で格納します。したがって、uに値String a="a";を代入するとオブジェクトが作成され、値がその中に格納されます。値a="ty"を代入している場合には、別のオブジェクトがその中に値を格納します。明確に理解したいのであれば、Stringhas codeをチェックしてください。

2
Selva

次のコードで違いが解消されると思います。

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);
2
Ankit Sinha

代入文でオブジェクトを変更しているのではなく、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は変更されています。

2
dasblinkenlight

実際には新しい文字列への参照を取得しています。文字列自体は不変なので変更されません。これは関係があります。

見る

ウィキペディアの不変オブジェクト

2
Jordan

Stringは不変です。つまり、Stringオブジェクトの内容は、一度作成されると変更できません。内容を変更したい場合は、StringではなくStringBuffer/StringBuilderを選択してください。 StringBufferとStringBuilderは可変クラスです。

1
user095736

以下のコードがあなたの疑問を明確にすることを願っています:

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

1
technolisha

おそらく上記のすべての答えは正しいのですが、私の答えは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
1

一部のオブジェクトbarが可変オブジェクトfooへの参照を保持し、その状態の一部をfooの状態の可変部分にカプセル化する場合fooのこれらの側面を変更できるコードは、barに実際に触れることなく、barの状態の対応する側面を変更できます。その存在さえ知っている。一般的に、これは、可変オブジェクトを使用して自身の状態をカプセル化するオブジェクトは、それらのオブジェクトへの参照が予期せず変更される可能性のあるコードにさらされないことを保証する必要があることを意味します。対照的に、barがオブジェクトmooへの参照を保持し、mooの恒等性以外の不変の側面のみを使用する場合その状態をカプセル化するために、barmooを外部コードに自由に公開できます。

1
supercat

参照だけが変化しています。最初のaは文字列 "a"を参照していましたが、後であなたはそれを "ty"に変更しました。文字列 "a"は変わりません。

1
Bhesh Gurung

あなたの例では、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"を出力します。

1
ruakh