web-dev-qa-db-ja.com

Javaの文字列の不変性

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

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。私は今それを得ていますが、1つのフォローアップの質問です。次のコードはどうですか:

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

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

これは、2つのオブジェクト(「ミシシッピ州」と「M!ss!ss!pp!」)が再び作成され、str参照がreplace()メソッドの後に別のオブジェクトを指すことを意味しますか?

207
Light_handle

strはオブジェクトではなく、オブジェクトへの参照です。 "Hello""Help!"は、2つの異なるStringオブジェクトです。したがって、strpoints to a string。 points toを変更できますが、points atを変更することはできません。

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

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

今、何もありません1 s1の値に影響するs2に対して実行できます。これらは同じオブジェクト-文字列"Hello"-を参照しますが、そのオブジェクトは不変であるため変更できません。

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

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

ここでは、オブジェクトの変更と参照の変更の違いを確認します。 s2は、s1が最初に設定するのと同じオブジェクトを引き続き指します。 s1"Help!"に設定すると、referenceのみが変更されますが、最初に参照したStringオブジェクトは変更されません。

文字列were mutableの場合、次のようなことができます。

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 実際、それはis文字列(および他の不変オブジェクト)を変更することが可能です。リフレクションが必要であり、非常に危険です。実際にプログラムを破壊することに興味がない限り、決して使用しないでください。

293
gustafc

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

文字列"Hello"および"Help!"を含むStringオブジェクトは値を変更できないため、不変です。

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

str参照が変更されないようにする1つの方法は、finalとして宣言することです。

final String STR = "Hello";

現在、別のStringSTRに割り当てようとすると、コンパイルエラーが発生します。

23
coobird

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

それらを読みましたか?はい。良い。

String str = new String();

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

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

str --- > ""

str  = "Hello";

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

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

str -+   ""
     +-> "Hello"

str = "Help!";

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

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

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

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

String s1 = "hello";

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

String s2 = s1;

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

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

両方の参照は同じ文字列を指しているため、次のように同じ値を出力します。

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

Stringimmutableですが、代入が可能なため、s1は新しい値stackを参照します。

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

しかし、s2を指しているオブジェクトはhelloのままになります。

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

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

Stringは不変であるため、Java Virtual Machineでは、メソッドによって文字列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の値を変更および変更できます。これが少しでも役立つことを願っています。

7
Shailesh Sonare

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

6
daveb

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

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!
5

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

5
akf

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

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

3
Preston Guillot

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

3
twolfe18

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オブジェクトを返します
2
etlapa

つかいます:

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
Nihar nayak

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

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

1つの文字列"Mississippi"を作成し、それを文字列プールに追加して、strがミシシッピを指すようにします。

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)こんにちは
2)世界
3)HelloWorld

2
Nirav Chhatrola

Stringは不変であるため、関数の戻り値をstringに割り当てない場合、変更は発生しません。そのため、質問では、スワップ関数の戻り値を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; } }
0
Shivam Shukla

または、次を試すことができます:

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
user1554868

簡単な例で説明します


任意の文字配列を検討してください:例char a [] = {'h'、 'e'、 'l'、 'l'、 'o'};および文字列:String s = "hello";


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

例えば.

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

s2には「hel」が付きます;

0

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

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

0
Afzal Ahmad

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

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

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

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

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

0
Uddhav Gautam

不変性は、文字列自体を変更できないということです。 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
Sumedha Khatter

答えは遅かったが、JavaのStringクラスの作成者から簡潔なメッセージを書きたかった

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

このドキュメントから、文字列を変更するものはすべてdifferentオブジェクト(新規またはインターンおよび古い可能性がある)を返すことができます。これについてのささいなヒントは、関数シグネチャから得られるはずです。 「なぜ、オブジェクトの関数がステータスではなくオブジェクトを返すようにしたのですか?」.

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
Ajeet Ganga

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

  1. New String()を呼び出したとき。
  2. 「hello」を割り当てたとき。
  3. 「help!」を割り当てたとき。

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

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

0

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

ただし、データを割り当てると、Stringsコンテンツが変更されます。

    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
Loshen Naicker

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」は参照なしでプール内で失われます。

0
Virtual

文字列不変です。つまり、変更できるのはreferenceのみです。


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
DeathRs

String is immutableは、オブジェクト自体を変更できないことを意味しますが、オブジェクトへの参照は変更できます。 a = "ty"を呼び出したとき、実際には、文字列リテラル "ty"によって作成された新しいオブジェクトへの参照を変更しています。オブジェクトの変更とは、メソッドを使用してフィールドの1つを変更することです(または、フィールドはパブリックであり、最終ではないため、メソッド経由でアクセスせずに外部から更新できます)。

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
E2rabi

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
martynas