Java中字符串的不变性

Immutability of Strings in Java

考虑the following example。P></

1
2
3
4
5
6
7
String str = new String();

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

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

现在,在Java字符串对象是immutable。那么如何为对象指定值strcan be the"帮助!"这不变性。contradicting of the不是Java的字符串?我可以对着风咒骂Please解释不变性概念of the’?P></

编辑:P></

好吧。现在我要它,但只是一个后续问题。尾巴:the following about是什么P></

1
2
3
4
5
String str ="Mississippi";
System.out.println(str); // prints Mississippi

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

does that are created this对象的二次均值("密西西比"和的"M!SS!SS!聚丙烯(PP)!"strand the different to a对象参考点后replace()法?P></


str不是一个对象,它是一个对象的引用。"Hello""Help!"是两个不同的String对象。因此,str指向一个字符串。你可以改变它指向什么,但不能改变它指向什么。

以该代码为例:

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

现在,我们不能对s1做任何影响s2价值的事情。它们指的是同一个对象——字符串"Hello",但该对象是不可变的,因此不能更改。

如果我们这样做:

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

这里我们看到了改变一个对象和改变一个引用之间的区别。s2仍然指向与我们最初设置s1指向相同的对象。将s1设置为"Help!"只会更改引用,而它最初引用的String对象保持不变。

如果字符串是可变的,我们可以这样做:

1
2
3
4
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中找到——只要您想知道某个东西是如何工作的,就需要查看pro提示)您可以看到它的作用是:

  • 如果当前字符串中出现一个或多个oldChar,则复制当前字符串,其中oldChar的所有出现都替换为newChar
  • 如果当前字符串中不存在oldChar,则返回当前字符串。

是的,"Mississippi".replace('i', '!')创建了一个新的String对象。同样,以下内容适用:

1
2
3
4
5
6
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实际上,可以改变字符串(和其他不变的对象)。它需要深思熟虑,非常非常危险,除非你真的有兴趣破坏程序,否则永远不应该使用它。


str引用的对象可以更改,但实际的String对象本身不能更改。

包含字符串"Hello""Help!"String对象无法更改其值,因此它们是不可变的。

String对象的不变性并不意味着指向该对象的引用不能更改。

防止str引用更改的一种方法是将其声明为final

1
final String STR ="Hello";

现在,试图将另一个String分配给str将导致编译错误。


轻手柄,我建议你读一下杯子的尺寸——一个关于变量和传递值的故事(杯子尺寸继续)。这对阅读上面的文章有很大帮助。

你读过吗?对。很好。

1
String str = new String();

这将创建一个新的"远程控制",称为"str",并将其设置为值new String()(或"")。

例如,在内存中,这会创建:

1
str --- >""
1
str  ="Hello";

然后更改遥控器"str",但不修改原始字符串""

例如,在内存中,这会创建:

1
2
str -+  ""
     +->"Hello"
1
str ="Help!";

然后更改遥控器"str",但不修改原始字符串""或遥控器当前指向的对象。

例如,在内存中,这会创建:

1
2
3
str -+  ""
     |  "Hello"
     +->"Help!"


我们把它分成几个部分吧

1
String s1 ="hello";

此语句创建包含hello的字符串并占用内存中的空间,即常量字符串池中的空间,并将其分配给引用对象s1。

1
String s2 = s1;

此语句将相同的字符串hello赋给新引用s2

1
2
3
4
         __________
        |          |
s1 ---->|  hello   |<----- s2
        |__________|

两个引用都指向同一个字符串,因此输出的值如下所示。

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

虽然字符串是不可变的,但可以进行赋值,因此s1现在将引用新的值堆栈。

1
2
3
4
5
s1 ="stack";    
         __________
        |          |
s1 ---->|  stack   |
        |__________|

但是,对于指向hello的s2对象,它将保持原样。

1
2
3
4
5
6
7
         __________
        |          |
s2 ---->|  hello   |
        |__________|

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

由于字符串是不可变的,所以Java虚拟机不允许我们用它的方法修改字符串S1。它将在池中创建所有新的字符串对象,如下所示。

1
2
3
4
5
6
7
8
9
10
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

注意,如果字符串将是可变的,那么输出将是

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

现在您可能会惊讶为什么String有这样的方法来修改Cuto()。下面的片段会消除你的困惑。

1
s1 = s1.concat(" overflow");

这里,我们将字符串的修改值赋值返回到S1引用。

1
2
3
4
5
6
7
8
         ___________________
        |                   |
s1 ---->|  stack overflow   |
        |___________________|


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

这就是为什么Java将字符串定义为最终类的原因,否则任何人都可以修改和更改字符串的值。希望这会有点帮助。


str首次引用的字符串对象没有被更改,您所做的只是使str引用一个新的字符串对象。


字符串不会更改,对它的引用将更改。您将不可变性与final字段的概念混淆在一起。如果一个字段声明为final,则一旦该字段被分配,就不能重新分配。


关于问题的替换部分,请尝试以下操作:

1
2
3
4
5
6
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!

不变性意味着实例化对象的值不能更改,您永远不能将"hello"转换为"help!".

变量str是对一个对象的引用,当您为str分配一个新值时,您不会更改它引用的对象的值,而是引用另一个对象。


虽然Java试图忽略它,但EDCOX1的0个""只不过是一个指针。这意味着当你第一次写str ="Hello";时,你创建了一个str指向的对象。当您通过编写EDCOX1(18)来重新分配EDCOX1(0)时,创建一个新的对象,并且当Java感觉到它时,旧的EDCOX1×3的对象会被垃圾收集。


用途:

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

如果在这里看到,我使用concat方法来更改原始字符串,即"new string"加了一个字符串"added string",但我还是像以前一样得到了输出,因此它证明了您不能更改string类对象的引用,但是如果您通过stringbuilder类执行此操作,它将起作用。如下所示。

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

字符串类是不可变的,不能更改不可变对象的值。但在字符串的情况下,如果更改字符串的值,它将在字符串池中创建新的字符串,而不是对该值的字符串引用。所以字符串是不可变的。举个例子,

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

它将创建一个字符串"Missippi",并将其添加到字符串池中所以现在str指向密西西比。

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

但在上述操作之后,另一个字符串将被创建为"m!SS!SS!PP!"它将被添加到字符串池中。和现在str指向m!SS!SS!PP!不是密西西比州。

因此,通过这种方式,当您更改字符串对象的值时,它将再创建一个对象,并将其添加到字符串池中。

再举一个例子

1
2
3
String s1 ="Hello";
String s2 ="World";
String s = s1 + s2;

上面这三行将向字符串池添加三个字符串对象。
1)你好,BR/>2)世界< BR>3)地狱世界


就像莱纳斯·托尔瓦兹说的:

Talk is cheap. Show me the code

看看这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
    }
}

输出是

1
2
3
4
5
6
7
a==b? true
a: Mississippi
b: Mississippi
c==d? false
c: Mississippi
d: Mississippi
a==e? true

所以,记住两件事:

  • 字符串是不可变的,直到应用一个操作并创建新的方法(C&D D案例)。
  • 如果两个参数相同,则替换方法返回相同的字符串对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    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());
    }
}

会制造出这样的东西

十四亿一千九百三十五万八千三百六十九十四亿一千九百三十五万八千三百六十九

十万三千零五十六六千五百零七万八千七百七十七

不可变对象的目的是,一旦分配了值,就不应该更改它的值。每次尝试根据实现更改新对象时,它都会返回新对象。注意:可以使用StringBuffer而不是String来避免这种情况。


或者你可以尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
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());
 }
 }

这将显示哈希代码如何更改。


字符串是不可变的。这意味着我们只能更改引用。

1
2
3
4
5
6
7
8
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

在爪哇中,对象通常通过引用访问。在您的代码中,str是一个引用,它首先被分配给"hello"(一个自动创建的对象或从常量池中提取的对象),然后您分配给另一个对象"help!"同一参考。要注意的一点是,引用是相同的,并且是修改过的,但是对象是不同的。在代码中还有一件事,您访问了三个对象,

  • 当您调用new string()时。
  • 当你分配"你好"时。
  • 当你分配"帮助!".
  • 调用new string()将创建一个新对象,即使它存在于字符串池中,因此通常不应使用它。要将从新字符串()创建的字符串放入字符串池,可以尝试使用intern()方法。

    我希望这有帮助。


    对于那些想知道如何打破爪哇字符串不变性的人…

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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();
            }

        }
    }

    产量

    1
    2
    3
    true
    I am   mutable
    I am   mutable

    Java中的字符串在不可变的和最终的意思是它不能被改变或修改:

    案例1:

    1
    2
    3
    4
    5
    6
    7
    class TestClass{  
     public static void main(String args[]){  
       String str ="ABC";  
       str.concat("DEF");  
       System.out.println(str);  
     }  
    }

    Output: ABC

    Reason: The object reference str is not changed in fact a new object
    "DEF" is created which is in the pool and have no reference at all
    (i.e lost).

    案例2:

    1
    2
    3
    4
    5
    6
    7
    class TestClass{  
     public static void main(String args[]){  
       String str="ABC";  
       str=str.concat("DEF");  
       System.out.println(str);  
     }  
    }

    Output: ABCDEF

    Reason: In this case str is now referring to a new object"ABCDEF"
    hence it prints ABCDEF i.e. previous str object"ABC" is lost in pool with no reference.


    字符串是不可变的,意味着不能更改对象本身,但可以更改对对象的引用。当您调用a="ty"时,实际上是将a的引用更改为字符串"ty"创建的新对象。更改对象意味着使用其方法更改其某个字段(或者字段是公共的,不是最终的,这样可以从外部更新它们,而无需通过方法访问它们),例如:

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

    在不可变类(声明为final,以防止通过继承进行修改)中(其方法不能修改其字段,而且字段始终是私有的,建议为final),例如string,不能更改当前字符串,但可以返回新字符串,即:

    1
    2
    3
    4
    5
    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"


    这里不可变意味着实例可以指向其他引用,但字符串的原始内容不会在原始引用处修改。让我用你给出的第一个例子来解释。第一个str指的是"你好",直到现在还可以。第二次指向"帮助!".这里str开始指向"帮助!""hello"字符串的引用丢失,我们无法恢复。

    实际上,当str试图修改现有内容时,将生成另一个新字符串,str将开始指向该引用。所以我们看到原始引用处的字符串没有被修改,但在其引用处是安全的,并且对象的实例开始指向不同的引用,所以不可变性是保持不变的。


    答案太晚了,但是想从Java中的String类的作者那里给出一个简洁的消息

    Strings are constant; their values cannot be changed after they are
    created. String buffers support mutable strings. Because String
    objects are immutable they can be shared.

    从这个文档中可以得出,任何更改字符串、返回不同对象(可以是新的、实习的和旧的)的操作。关于这个的不那么微妙的提示应该来自函数签名。想想看,"为什么他们让一个对象上的函数返回一个对象而不是状态?".

    1
    public String replace(char oldChar, char newChar)

    还有一个使此行为显式的源(来自替换函数文档)

    Returns a new string resulting from replacing all occurrences of
    oldChar in this string with newChar.

    资料来源:http://DOCS.Oracle .COM/JavaSe/ 7 /DOCS/API/Java/Lang/String。HTML替换(char,%20char)

    • 作者李博因顿
    • 作者阿瑟·范霍夫
    • 作者Martin Buchholz
    • 作者乌尔夫·齐比斯

    来源:字符串的javadoc。


    对象字符串-方法本身被设置为"不可变"。此操作不产生更改:"letters.replace"("bbb","aaa");

    但是,分配数据确实会导致字符串内容发生更改:

    1
    2
    3
    4
    5
    6
    7
    8
        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);

    //字符串对象的哈希代码不变。


    因为字符串是不可变的,所以如果不将函数的返回值赋给字符串,则不会发生更改。因此,在您的问题中,请将交换函数的返回值赋给S。

    s=交换(s,n1,n2);则字符串s的值将更改。

    在编写程序以获取一些排列字符串时,我也得到了不变的值(虽然它没有给出所有排列,但这是为了回答您的问题)

    下面是一个例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    > import java.io.*;&nbsp; public class MyString { public static void
    > main(String []args)throws IOException {&nbsp; BufferedReader br=new
    > BufferedReader(new InputStreamReader(System.in));&nbsp; String
    > s=br.readLine().trim(); int n=0;int k=0;&nbsp; while(n!=s.length()) {
    > while(k<n){&nbsp; swap(s,k,n); System.out.println(s); swap(s,k,n); k++; }
    > n++; } }&nbsp; 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();
    > } }

    但我没有从上面的代码中获取字符串的排列值,因此我将swap函数的返回值分配给了字符串,并得到了字符串的更改值。在分配返回值之后,我得到了字符串的排列值。

    1
    2
    3
    4
    5
    6
    7
    /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));&nbsp;
    String s=br.readLine().trim(); int n=0;int k=0;&nbsp;
    while(n!=s.length()){ while(k<n){ s=swap(s,k,n);&nbsp;
    System.out.println(s); s=swap(s,k,n); k++; } n++; } }&nbsp;
    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; } }


    如果HELLO是字符串,则不能将HELLO更改为HILLO。此属性称为不可变属性。

    您可以有多个指针字符串变量来指向hello字符串。

    但如果hello是char数组,那么可以将hello改为hillo。如,

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

    编程语言具有不变的数据变量,因此它可以用作键、值对中的键。


    我可以说,不变性是您不能更改字符串本身。假设您有字符串x,其值为"abc"。现在您不能更改字符串,也就是说,您不能更改"abc"中的任何字符。

    如果必须更改字符串中的任何字符,可以使用字符数组并对其进行变异,或者使用StringBuilder。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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);

    输出:

    1
    2
    hj
    sj