Java中string的不变性

考虑下面的例子。

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中string的不变性相矛盾? 任何人都可以解释我的不变性的确切概念吗?

编辑:

好。 我现在正在得到它,但只是一个后续问题。 下面的代码是什么?

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

这是否意味着再次创build两个对象(“密西西比”和“M!ss!pp!”),并且引用strreplace()方法之后指向另一个对象?

str不是一个对象,它是一个对象的引用。 "Hello""Help!" 是两个不同的String对象。 因此, str 指向一个string。 你可以改变它指向的内容 ,但不是它指向的内容

以此代码为例:

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

现在,我们无法做到s1会影响s2的价值。 他们引用同一个对象 – string"Hello" – 但该对象是不可变的,因此不能改变。

如果我们做这样的事情:

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

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

如果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中find) – 一个专业技巧是在您想知道某个方面真正起作用的时候看看那里),您可以看到它的确如下:

  • 如果在当前string中有一个或多个oldChar出现,则复制当前string的所有出现的oldCharnewCharreplace。
  • 如果oldChar不存在于当前string中,则返回当前string。

所以是的, "Mississippi".replace('i', '!')创build一个新的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其实,可以改变string(和其他不可变的对象)。 它需要反思,是非常非常危险的,永远不要使用,除非你真的有兴趣销毁程序。

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

包含string"Hello""Help!"String对象 不能改变他们的价值观,因此他们是不变的。

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

一个可以阻止大数据参考变化的方法是将其声明为final

 final String STR = "Hello"; 

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

Light_handle我build议你读一下Cup Size – 一个关于variables和Pass-by-Value请注意 的故事 (Cup Size continued) 。 阅读上面的post时,这会有很大的帮助。

你读过吗? 是。 好。

 String str = new String(); 

这将创build一个名为“ str ”的新“遥控器”,并将其设置为new String() (或"" )值。

例如在内存中,这会产生:

 str --- > "" 

 str = "Hello"; 

然后这改变遥控器“ str ”,但不修改原来的string""

例如在内存中,这会产生:

 str -+ "" +-> "Hello" 

 str = "Help!"; 

这会改变遥控器“ str ”,但不会修改原始string""或遥控器当前指向的对象。

例如在内存中,这会产生:

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

str第一次引用的string对象没有被改变,你所做的只是使用str引用一个新的string对象。

让我们把它分解成一些部分

 String s1 = "hello"; 

这个语句创build包含hello的string,并在内存中占用空间,即在常量string池中 ,并将其分配给引用对象s1

 String s2 = s1; 

这个语句为新引用s2分配了相同的stringhello

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

两个引用都指向相同的string,所以输出相同的值,如下所示。

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

虽然String不可变的 ,但赋值是可能的,所以s1现在将引用新的值

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

但是,指向hello的 s2对象怎么样呢?

  __________ | | s2 ---->| hello | |__________| out.println(s1); // o/p: stack out.println(s2); // o/p: hello 

由于String是不可变的, Java虚拟机不允许我们通过它的方法修改strings1 。 它会按如下方式创build池中的所有新的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"); 

在这里,我们将修改后的string赋值给s1引用。

  ___________________ | | s1 ---->| stack overflow | |___________________| out.println(s1); // o/p: stack overflow out.println(s2); // o/p: hello 

这就是为什么Java决定将String作为最终的类,否则任何人都可以修改和更改string的值。 希望这会有所帮助。

string不会改变,它的引用将会。 你把final领域的概念与不变性混为一谈。 如果一个字段被声明为final ,那么一旦它被赋值,它就不能被重新分配。

关于你的问题的replace部分,试试这个:

 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!”。

variablesstr是一个对象的引用,当你为str指定一个新的值时,你并没有改变它所引用的对象的值,那么你引用了一个不同的对象。

尽pipejava试图忽略它,但str只不过是一个指针而已。 这意味着当你第一次写str = "Hello"; ,你创build一个str指向的对象。 当你通过写str = "Help!";重新分配str , ,一个新的对象被创build,并且只要java感觉就像旧的"Hello"对象一样被垃圾收集。

使用:

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

如果你在这里看到我使用concat方法来改变原来的string,即“新string”string“增加string”,但我仍然得到了前面的输出,因此它certificate你不能改变引用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 

String类是不可变的,你不能改变不可变对象的值。 但是在string的情况下,如果你改变string的值,它将在string池中创build新的string,而不是你的string引用那个值,而不是旧的string。 所以通过这种方式string是不可变的。 让我们以你为例,

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

它会创build一个string“密西西比” ,并将其添加到string池,所以现在str是指向密西西比州。

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

但是经过上面的操作,就会创build另一个string“M!ss!ss!pp!” 它会被添加到string池。 现在str正指向M!ss!ss!pp!,而不是密西西比河。

所以通过这种方式,当你将改变string对象的值时,它将创build一个更多的对象,并将其添加到string池。

让我们再举一个例子

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

这上面三行将把string的三个对象添加到string池中。
1)你好
2)世界
3)HelloWorld

在不可变和最终Java中的string只是意味着它不能被改变或修改:

情况1:

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

输出:ABC

原因:对象引用str实际上没有改变,创build了一个新的对象“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”在池中丢失,没有引用。

就像Linus Tvvards所说:

谈话很便宜。 给我看代码

看看这个:

 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 

所以,记住两件事情:

  • string是不可变的,直到你应用一个方法来处理和创build一个新的(c&d的情况)。
  • 如果两个参数相同,Replace方法将返回相同的String对象

对于那些想知道如何打破Java中的string不变性…

 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 

string不可变的 。 这意味着我们只能改变参考


 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 

在Java中,对象通常通过引用来访问。 在你的代码片段中,str是首先分配给“Hello”(一个自动创build的对象或从常量池中获取)的引用,然后为其分配另一个对象“Help!”。 以相同的参考。 需要注意的一点是引用是相同的和修改的,但是对象是不同的。 你的代码中还有一件事你访问了三个对象,

  1. 当你调用新的String()。
  2. 当你分配“你好”。
  3. 当你分配“帮助!”。

调用新的String()会创build一个新的对象,即使它存在于string池中,所以一般不应该使用它。 要将从新的String()创build的string放入string池,可以尝试intern()方法。

我希望这有帮助。

不变性我可以说的是,你不能改变string本身。 假设你有String x,其值是“abc”。 现在你不能改变string,也就是说你不能改变“abc”中的任何字符。

如果必须更改string中的任何字符,则可以使用字符数组并对其进行变异或使用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 

或者你可以尝试:

 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()); } } 

这将显示哈希码如何改变。

string是不可变的,意味着你不能改变对象本身,但你可以改变对象的引用。 当你调用一个=“ty”时,你实际上正在将a的引用改为一个由string文字“ty”创build的新对象。 改变一个对象意味着使用它的方法来改变它的一个字段(或者这些字段是公开的而不是最终的,这样它们可以从外部更新而不用通过方法访问它们),例如:

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

虽然在一个不可变的类中(声明为final,为了防止通过inheritance进行修改)(它的方法不能修改它的字段,而且这些字段总是私有的,推荐是final),比如String,你不能改变当前的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" 

这里的不变性意味着实例可以指向其他引用,但是string的原始内容在原始引用时不会被修改。 让我通过你给出的第一个例子来解释。 首先是指向“你好”,它的确定这个。 第二次指向“帮助!”。 这里str开始指向“Help!” 并且“Hello”string的引用丢失了,我们不能得到它。

实际上,当str尝试修改现有内容时,将会生成另一个新string,str将开始指向该引用。 所以我们看到在原始引用的string没有被修改,但是在它的引用和对象的实例开始指向不同的引用是安全的,所以不变性被节省。

超级迟到的答案,但想要从Java的String类作者简要的消息

string是不变的; 他们的价值创造后不能改变。 string缓冲区支持可变string。 因为String对象是不可变的,所以它们可以共享。

从这个文档中可以得出任何改变string的东西,都会返回不同的对象(可能是新的或者是实际的,而且是旧的)。 关于这个不太微妙的暗示应该来自函数签名。 想一想,“为什么他们在一个对象上返回一个对象而不是状态?”。

 public String replace(char oldChar, char newChar) 

还有一个来源,使这种行为明确(从replacefunction文件)

返回由newCharreplace此string中出现的所有oldChar所产生的新string。

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

  • 作者Lee Boynton
  • 作者阿瑟·范·霍夫
  • 作者Martin Buchholz
  • 作者Ulf Zibis

来源:String的JavaDoc。

对象string – 方法本身被设置为“不可变的”。 此操作不会产生任何更改:“letters.replace(”bbb“,”aaa“);”

但是分配数据确实会导致string内容发生变化:

  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); 

//stringObject的哈希码不会改变。