Javaにおける文字列の不変性


218

次の例を考えてみましょう。

String str = new String();

str  = "Hello";
System.out.println(str);  //Prints Hello

str = "Help!";
System.out.println(str);  //Prints Help!

現在、Javaでは、Stringオブジェクトは不変です。次に、オブジェクトにstr値「Help!」を割り当てることができる理由を説明します。これは、Javaの文字列の不変性と矛盾していませんか?不変性の正確な概念を誰かに説明してもらえますか?

編集:

OK。私はそれを得ています、しかしただ一つのフォローアップ質問。次のコードはどうですか:

String str = "Mississippi"; 
System.out.println(str); // prints Mississippi 

str = str.replace("i", "!"); 
System.out.println(str); // prints M!ss!ss!pp! 

これは、2つのオブジェクト( "Mississippi"および "M!ss!ss!pp!")が再度作成され、参照がメソッドのstr後の別のオブジェクトを指すことを意味しreplace()ますか?


strはオブジェクトそのものではなく、参照のみです
ahmednabil88

回答:


316

strオブジェクトではなく、オブジェクトへの参照です。"Hello""Help!"2つの異なるStringオブジェクトです。したがって、文字列をstr 指します。あなたはそれが何を変更することができますを指し、それはそのことではなく、で指し示します

たとえば、次のコードを見てください。

String s1 = "Hello";
String s2 = s1;
// s1 and s2 now point at the same string - "Hello"

さて、の値に影響を与える可能性のある1のことは何もありません。それらは同じオブジェクト(文字列)を参照しますが、そのオブジェクトは不変であるため、変更できません。s1s2"Hello"

このようなことをすると:

s1 = "Help!";
System.out.println(s2); // still prints "Hello"

ここで、オブジェクトの変更と参照の変更の違いを確認します。s2それでも、最初s1にポイントするように設定したのと同じオブジェクトを指します。に設定s1すると参照"Help!"のみが変更され、元々参照していたオブジェクトは変更されません。String

文字列変更可能な場合、次のようなことができます。

String s1 = "Hello";
String s2 = s1;
s1.setCharAt(1, 'a'); // Fictional method that sets character at a given pos in string
System.out.println(s2); // Prints "Hallo"

OPの編集に応答するための編集:

String.replace(char、char)ソースコード( JDKインストールディレクトリのsrc.zipにもあります-何かが実際にどのように機能するのか疑問に思ったらいつでもそこを確認することをお勧めします)を見ると、それは次のとおりです:

  • oldChar現在の文字列にが1つ以上出現する場合は、現在の文字列のすべての出現箇所oldCharがに置き換えられたコピーを作成しnewCharます。
  • oldChar現在の文字列にが存在しない場合は、現在の文字列を返します。

したがって、はい、"Mississippi".replace('i', '!')新しいStringオブジェクトを作成します。繰り返しになりますが、次のことが当てはまります。

String s1 = "Mississippi";
String s2 = s1;
s1 = s1.replace('i', '!');
System.out.println(s1); // Prints "M!ss!ss!pp!"
System.out.println(s2); // Prints "Mississippi"
System.out.println(s1 == s2); // Prints "false" as s1 and s2 are two different objects

今のあなたの宿題は、あなたs1 = s1.replace('i', '!');s1 = s1.replace('Q', '!');:)に変更した場合に上記のコードが何をするかを見ることです。


1実際には、文字列(およびその他の不変オブジェクト)を変更すること可能です。これはリフレクションを必要とし、非常に非常に危険であり、プログラムを破壊することに本当に興味がない限り、決して使用すべきではありません。


15
架空の方法の+1は、不変オブジェクトと他のオブジェクトの違いを示しています。
Zappi

1
正しい例と明確な説明についてgustafcに感謝します。しかし、質問の編集された部分に答えることができますか?それで私の理解が明確になります。
Light_handle 2009年

17
このような答えは今まで見たことがありません。すべての詳細を議論しました。
Michael 'Maik' Ardan 2013

+1これがアイデアです。Javaの不変オブジェクトは値によるコピーのようなもので、文字列への2つの参照を持つことができますが、不変なので、それらを2つの別々の文字列と見なす必要があります。その他
Khaled.K 2014年

1
Java-知っていると思うほど、実際に知ることは少なくなります。
Zeeshan

23

str参照するオブジェクトは変更できますが、実際のStringオブジェクト自体は変更できません。

String文字列を含むオブジェクト"Hello"とは"Help!"、したがって、彼らは不変で、その値を変更することはできません。

Stringオブジェクトの不変性は、オブジェクトを指す参照が変更できないことを意味しません。

1つを防ぐことができる1つの方法 str参照が変更され参照を次のように宣言することfinalです。

final String STR = "Hello";

今、別のものを代入しようとStringするためにSTRコンパイルエラーが発生します。


ただし、この場合、Stringオブジェクトは「str」であり、最初に「Hello」という値が含まれ、次に新しい値「Help!」が割り当てられます。「文字列 "Hello"と "Help!"を含むStringオブジェクトは値を変更できないため、不変です。」とはどういう意味ですか?これがばかげた質問ならご容赦ください。しかし、ivはそれをクリアしました...
Light_handle 2009年

2
Cでプログラミングしようとした人はいますか?ポインタに関する入門書を読むだけで、coobirdの答えを完全に理解できます。
ライアンフェルナンデス

参照してください...これは避けたいことです...あなたは素晴らしいプログラマーです...そしてここでJavaを習得しようとしているだけです...ですから、私の質問に正しく答えることができれば、答えてください。 。
は、light_handle

参照とオブジェクトを混同している- strそれは「オブジェクト」ではなく、オブジェクトへの参照です。あなたがいる場合String str = "Hello";に続いてString anotherReference = str;2つのStringオブジェクトを持っていないあなた、あなたは一つのオブジェクトそれ(へ(リテラル「こんにちは」)及び2つの参照持っているstrとしanotherReference)。
ネイト

私には編集する権限がありませんが、そうした場合、私はcoobirdsの最初の文を次のように編集します。
j3App 2017年

10

Light_handle カップサイズを読むことをお勧めします-変数値渡しの(カップサイズは続きます)。これは、上記の投稿を読むときに非常に役立ちます。

読みましたか?はい。良い。

String str = new String();

これにより、「」という新しい「リモートコントロール」が作成さstrれ、その値が設定されますnew String()(または"")に。

例えば、これはメモリ内で作成されます:

str --- > ""

str  = "Hello";

これにより、リモートコントロール " str"が変更されますが、元の文字列は変更されません。""

例えば、これはメモリ内で作成されます:

str -+   ""
     +-> "Hello"

str = "Help!";

これにより、リモートコントロール " str"が変更されますが、元の文字列は変更されません。""やリモートコントロールが現在指しているオブジェクト。

例えば、これはメモリ内で作成されます:

str -+   ""
     |   "Hello"
     +-> "Help!"

「」と「こんにちは」はガベージコレクションされますか?
Prabin Timsina、2016

@PrabinTimsinaこれは本当に新しい質問になるはずです。それは答えている:stackoverflow.com/questions/15324143/...
マイケル・ロイド・リーMLKを

9

いくつかの部分に分けましょう

String s1 = "hello";

このステートメントは、helloを含む文字列を作成し、メモリ内(つまり、定数文字列プール)のスペースを占有して、参照オブジェクトs1に割り当てます

String s2 = s1;

このステートメントは、同じ文字列helloを新しい参照s2に割り当てます

         __________
        |          |
s1 ---->|  hello   |<----- s2
        |__________| 

どちらの参照も同じ文字列を指しているため、次のように同じ値を出力します。

out.println(s1);    // o/p: hello
out.println(s2);    // o/p: hello

けれども文字列がある不変ので、割り当てが可能することができs1は今、新しい値を参照しますスタック

s1 = "stack";    
         __________
        |          |
s1 ---->|  stack   |
        |__________|

しかし、こんにちはを指すs2オブジェクトはどうなりますか。

         __________
        |          |
s2 ---->|  hello   |
        |__________|

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello

Stringは不変であるため、Java仮想マシンはそのメソッドで文字列s1を変更できません。次のように、プール内にすべての新しいStringオブジェクトが作成されます。

s1.concat(" overflow");

                 ___________________
                |                   |
s1.concat ----> |  stack overflow   |
                |___________________|

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello
out.println(s1.concat); // o/p: stack overflow

Stringが変更可能である場合、出力は次のようになります。

out.println(s1);    // o/p: stack overflow

Stringにconcat()のようなメソッドを変更する理由に驚くかもしれません。次のスニペットは混乱を解消します。

s1 = s1.concat(" overflow");

ここでは、文字列の変更された値をs1参照に割り当てています。

         ___________________
        |                   |
s1 ---->|  stack overflow   |
        |___________________|


out.println(s1);    // o/p: stack overflow
out.println(s2);    // o/p: hello

そのため、JavaはStringを最終クラスに決定しました。そうでなければ、誰でもstringの値を変更および変更できます。これが少し役立つことを願っています。


6

によって最初に参照されstrた文字列オブジェクトは変更されませんでしたstr。新しい文字列オブジェクトを参照させるだけでした。


5

文字列は変更されません。参照は変更されます。finalフィールドの概念と不変性を混同しています。フィールドがとして宣言されfinalている場合、いったん割り当てられると、再割り当てすることはできません。


5

あなたの質問の置換部分については、これを試してください:

String str = "Mississippi"; 
System.out.println(str); //Prints Mississippi 

String other = str.replace("i", "!"); 
System.out.println(str); //still prints Mississippi 
System.out.println(other);  // prints M!ss!ss!pp!

4

javaはそれを無視しようとしstrますが、ポインタに過ぎません。つまり、最初にを書いstr = "Hello";たときに、strポイントするオブジェクトを作成します。をstr書き込んstr = "Help!";で再割り当てすると、新しいオブジェクトが作成され、"Hello"Javaがそのように感じるたびに古いオブジェクトがガベージコレクションされます。


3

不変性は、インスタンス化されたオブジェクトの値が変更できないことを意味し、「Hello」を「Help!」に変えることはできません。

変数strはオブジェクトへの参照です。strに新しい値を割り当てると、参照するオブジェクトの値は変更されず、別のオブジェクトが参照されます。


3

文字列クラスは不変であり、不変オブジェクトの値を変更することはできません。しかし、文字列の場合、文字列の値を変更すると、文字列プールに新しい文字列が作成され、古い値ではなくその値への文​​字列参照よりも変更されます。このようにして、文字列は不変です。あなたの例を見てみましょう、

String str = "Mississippi";  
System.out.println(str); // prints Mississippi 

1つの文字列"Mississippi"を作成し、それを文字列プールに追加するので、strはMississippiを指します。

str = str.replace("i", "!");  
System.out.println(str); // prints M!ss!ss!pp! 

しかし、上記の操作の後、もう1つの文字列"M!ss!ss!pp!"が作成されます。 文字列プールに追加されます。そして今strはM!ss!ss!pp!を指しており、ミシシッピではありません。

このようにして、文字列オブジェクトの値を変更すると、1つ以上のオブジェクトが作成され、文字列プールに追加されます。

もう一つ例を挙げましょう

String s1 = "Hello"; 
String s2 = "World"; 
String s = s1 + s2;

この上の3行は、文字列の3つのオブジェクトを文字列プールに追加します。
1)Hello
2)World
3)HelloWorld


2

使用する:

String s = new String("New String");
s.concat(" Added String");
System.out.println("String reference -----> "+s); // Output: String reference -----> New String

ここにある場合、concatメソッドを使用して元の文字列を変更します。つまり、「新しい文字列」と文字列「追加された文字列」を変更しますが、以前と同じように出力が得られるため、オブジェクトの参照を変更できないことがわかります。 Stringクラスのですが、StringBuilderクラスでこれを行うと機能します。以下にリストします。

StringBuilder sb = new StringBuilder("New String");
sb.append(" Added String");
System.out.println("StringBuilder reference -----> "+sb);// Output: StringBuilder reference -----> New String Added String

2

Linus Tolvardsが言ったように:

口で言うだけなら簡単です。コードを見せて

これをみて:

public class Test{
    public static void main(String[] args){

        String a = "Mississippi";
        String b = "Mississippi";//String immutable property (same chars sequence), then same object

        String c = a.replace('i','I').replace('I','i');//This method creates a new String, then new object
        String d = b.replace('i','I').replace('I','i');//At this moment we have 3 String objects, a/b, c and d

        String e = a.replace('i','i');//If the arguments are the same, the object is not affected, then returns same object

        System.out.println( "a==b? " + (a==b) ); // Prints true, they are pointing to the same String object

        System.out.println( "a: " + a );
        System.out.println( "b: " + b );

        System.out.println( "c==d? " + (c==d) ); // Prints false, a new object was created on each one

        System.out.println( "c: " + c ); // Even the sequence of chars are the same, the object is different
        System.out.println( "d: " + d );

        System.out.println( "a==e? " + (a==e) ); // Same object, immutable property
    }
}

出力は

a==b? true
a: Mississippi
b: Mississippi
c==d? false
c: Mississippi
d: Mississippi
a==e? true

したがって、2つのことを覚えておいてください。

  • 文字列は、新しい文字列を操作および作成するメソッド(cおよびdの場合)を適用するまで不変です。
  • 両方のパラメーターが同じ場合、Replaceメソッドは同じStringオブジェクトを返します

0

Javaで文字列の不変性を壊す方法を知りたい人のために...

コード

import java.lang.reflect.Field;

public class StringImmutability {
    public static void main(String[] args) {
        String str1 = "I am immutable";
        String str2 = str1;

        try {
            Class str1Class = str1.getClass();
            Field str1Field = str1Class.getDeclaredField("value");

            str1Field.setAccessible(true);
            char[] valueChars = (char[]) str1Field.get(str1);

            valueChars[5] = ' ';
            valueChars[6] = ' ';

            System.out.println(str1 == str2);
            System.out.println(str1);
            System.out.println(str2);           
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

出力

true
I am   mutable
I am   mutable

0

文字列不変です。つまり、参照のみを変更できます


String a = "a";
System.out.println("String a is referencing to "+a); // Output: a

a.concat("b");
System.out.println("String a is referencing to "+a); // Output: a

a = a.concat("b");
System.out.println("String a has created a new reference and is now referencing to "+a); // Output: ab

0

Javaでは、オブジェクトは通常、参照によってアクセスされます。コードの一部では、strが最初に "Hello"(自動作成されたオブジェクトまたは定数プールからフェッチされた)に割り当てられた参照であり、次に別のオブジェクト "Help!"を割り当てました。同じ参照に。注意すべき点は、参照は同じで変更されていますが、オブジェクトが異なることです。コードでもう1つ、3つのオブジェクトにアクセスしました。

  1. new String()を呼び出したとき。
  2. 「こんにちは」を割り当てたとき。
  3. 「help!」を割り当てたとき。

new String()を呼び出すと、文字列プールに存在する場合でも新しいオブジェクトが作成されるため、通常は使用しないでください。new String()から作成された文字列を文字列プールに入れるには、intern()メソッドを試すことができます。

これがお役に立てば幸いです。


0

不変性とは、文字列自体を変更できないことです。String xがあり、その値が「abc」であるとします。これで、文字列を変更できなくなりました。つまり、「abc」の文字を変更できなくなりました。

文字列の文字を変更する必要がある場合は、文字配列を使用して変更するか、StringBuilderを使用できます。

String x = "abc";
x = "pot";
x = x + "hj";
x = x.substring(3);
System.out.println(x);

char x1[] = x.toCharArray();
x1[0] = 's';
String y = new String(x1);
System.out.println(y);

出力:

hj
sj

0

または、あなたは試すことができます:

public class Tester
{
public static void main(String[] args)
{
 String str = "Mississippi"; 
 System.out.println(str); // prints Mississippi 
 System.out.println(str.hashCode());

 str = str.replace("i", "!"); 
 System.out.println(str); // prints M!ss!ss!pp! 
 System.out.println(str.hashCode());
 }
 }

これは、ハッシュコードがどのように変化するかを示します。


0

文字列は不変です。つまり、オブジェクト自体は変更できませんが、オブジェクトへの参照は変更できます。a = "ty"を呼び出すと、実際にはaの参照を、文字列リテラル "ty"によって作成された新しいオブジェクトに変更します。オブジェクトを変更するということは、そのメソッドを使用してフィールドの1つを変更することを意味します(またはフィールドはfinalではなくpublicなので、メソッドを介してアクセスせずに外部から更新できます)。次に例を示します。

Foo x = new Foo("the field");
x.setField("a new field");
System.out.println(x.getField()); // prints "a new field"

不変のクラス(継承による変更を防ぐためにfinalとして宣言されている)(メソッドはそのフィールドを変更できず、フィールドは常に非公開であり、finalであることをお勧めします)、たとえばStringの場合、現在の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"

0

ここで不変性とは、インスタンスが他の参照を指すことができるが、文字列の元のコンテンツは元の参照で変更されないことを意味します。あなたからの最初の例で説明しましょう。最初のstrは「こんにちは」を指しています。二度目は「Help!」を指しています。ここでstrは「Help!」を指し始めました。"Hello"文字列の参照が失われ、それを取り戻すことはできません。

実際、strが既存のコンテンツを変更しようとすると、別の新しい文字列が生成され、strはその参照を指し始めます。したがって、元の参照の文字列は変更されていないことがわかりますが、その参照とオブジェクトのインスタンスは別の参照を指し始めているので安全であるため、不変性が保持されます。


0

答えにはかなり遅れましたが、JavaのStringクラスの作者からの簡潔なメッセージを入れたかったです

文字列は定数です。作成後に値を変更することはできません。文字列バッファは可変文字列をサポートします。Stringオブジェクトは不変なので、共有できます。

このドキュメントから、文字列を変更すると、別のオブジェクトが返されることがわかります(新しいオブジェクトまたはインターンされた古いオブジェクト)。これに関するそれほど微妙なヒントは、関数のシグネチャから取得する必要があります。それについて考えてみましょう、「なぜオブジェクトの関数がステータスではなくオブジェクトを返すようにしたのですか?」。

public String replace(char oldChar, char newChar) 

また、この動作を明示するもう1つのソース(置換関数のドキュメントから)

この文字列内のすべてのoldCharをnewCharで置き換えた結果の新しい文字列を返します。

ソース:https : //docs.oracle.com/javase/7/docs/api/java/lang/String.html#replace(char, % 20char)

  • 著者リー・ボイントン
  • 著者アーサーファンホフ
  • 著者マーティンブッフホルツ
  • 著者Ulf Zibis

ソース:StringのJavaDoc。


0

オブジェクト文字列-メソッド自体は「不変」に作成されます。このアクションによる変更はありません: "letters.replace(" bbb "、" aaa ");"

ただし、データを割り当てると、文字列の内容が変更されます。

    letters = "aaa";
    letters=null;
    System.out.println(letters);
    System.out.println(oB.hashCode());
    System.out.println(letters);
    letters = "bbbaaa";
    System.out.println(oB.hashCode());
    System.out.println(letters);

//文字列Objectのハッシュコードは変更されません。


0

HELLO文字列の場合、次のように変更HELLOすることはできませんHILLO。このプロパティは不変性プロパティと呼ばれます。

HELLO文字列を指す複数のポインタ文字列変数を持つことができます。

ただし、HELLOがchar配列の場合は、HELLOをHILLOに変更できます。例えば、

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

プログラミング言語には不変のデータ変数があるため、キーと値のペアのキーとして使用できます。


0

簡単な例で説明します


任意の文字配列を考慮してください。例:char a [] = {'h'、 'e'、 'l'、 'l'、 'o'}; そして文字列:String s = "hello";


文字配列では、配列の反復を使用して最後の3文字だけを印刷するなどの操作を実行できます。しかし文字列では、新しい文字列オブジェクトを作成し、必要な部分文字列をコピーする必要があります。そのアドレスは新しい文字列オブジェクトになります。

例えば

***String s="hello";
String s2=s.substrig(0,3);***

したがって、s2には「hel」があります。


-1

ImmutableおよびFinalのJavaの文字列は、変更または変更できないことを意味します。

ケース1:

class TestClass{  
 public static void main(String args[]){  
   String str = "ABC";  
   str.concat("DEF");  
   System.out.println(str);  
 }  
} 

出力:ABC

理由:オブジェクト参照strは実際には変更されません。プール内にあり、まったく参照がない(つまり失われた)新しいオブジェクト「DEF」が作成されます。

ケース2:

class TestClass{  
 public static void main(String args[]){  
   String str="ABC";  
   str=str.concat("DEF");  
   System.out.println(str);  
 }  
}  

出力:ABCDEF

理由:この場合、strは新しいオブジェクト「ABCDEF」を参照しているため、ABCDEFを出力します。つまり、以前のstrオブジェクト「ABC」は参照なしでプールで失われます。


-1

Stringは不変であるため、関数の戻り値をstring.soに割り当てない場合、変更は発生しません。質問でswap関数の戻り値の戻り値をsに割り当てます。

s = swap(s、n1、n2);その後、文字列sの値が変更されます。

いくつかの順列文字列を取得するプログラムを書いているときにも、変更されていない値を取得していました(すべての順列を提供しているわけではありませんが、これは、たとえば、質問に答えるためです)

ここに例があります。

> import java.io.*;  public class MyString { public static void
> main(String []args)throws IOException {  BufferedReader br=new
> BufferedReader(new InputStreamReader(System.in));  String
> s=br.readLine().trim(); int n=0;int k=0;  while(n!=s.length()) {
> while(k<n){  swap(s,k,n); System.out.println(s); swap(s,k,n); k++; }
> n++; } }  public static void swap(String s,int n1,int n2) { char temp;
> temp=s.charAt(n1); StringBuilder sb=new StringBuilder(s);
> sb.setCharAt(n1,s.charAt(n2)); sb.setCharAt(n2,temp); s=sb.toString();
> } }

しかし、上記のコードから文字列の置換された値を取得していなかったため、スワップ関数の戻り値を文字列に割り当て、文字列の変更された値を取得しました。戻り値を割り当てた後、文字列の置換された値を取得しました。

/import java.util.*; import java.io.*; public class MyString { public static void main(String []args)throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); 
String s=br.readLine().trim(); int n=0;int k=0; 
while(n!=s.length()){ while(k<n){ s=swap(s,k,n); 
System.out.println(s); s=swap(s,k,n); k++; } n++; } } 
public static String swap(String s,int n1,int n2){
char temp; temp=s.charAt(n1); StringBuilder sb=new StringBuilder(s); sb.setCharAt(n1,s.charAt(n2)); sb.setCharAt(n2,temp); s=sb.toString(); return s; } }

-1
    public final class String_Test {

    String name;
    List<String> list=new ArrayList<String>();

    public static void main(String[] args) {

        String_Test obj=new String_Test();
        obj.list.add("item");//List will point to a memory unit- i.e will have one Hashcode value #1234

        List<String> list2=obj.list; //lis1 also will point to same #1234

        obj.list.add("new item");//Hashcode of list is not altered- List is mutable, so reference remains same, only value in that memory location changes

        String name2=obj.name="Myname"; // name2 and name will point to same instance of string -Hashcode #5678
        obj.name = "second name";// String is Immutable- New String HAI is created and name will point to this new instance- bcoz of this Hashcode changes here #0089

        System.out.println(obj.list.hashCode());
        System.out.println(list2.hashCode());
        System.out.println(list3.hashCode());

        System.out.println("===========");
        System.out.println(obj.name.hashCode());
        System.out.println(name2.hashCode());
    }
}

このようなものを出します

1419358369 1419358369

103056 65078777

不変オブジェクトの目的は、一度割り当てられるとその値が変更されないようにすることです。実装に基づいて変更しようとするたびに、新しいオブジェクトを返します。注:これを回避するには、文字列の代わりに文字列バッファを使用できます。

あなたの最後の質問:: uには1つの参照があり、文字列プールには2つの文字列があります。参照がm!ss!ss!pp!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.