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!”),并且引用str
在replace()
方法之后指向另一个对象?
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的所有出现的oldChar
被newChar
replace。 - 如果
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!”。 以相同的参考。 需要注意的一点是引用是相同的和修改的,但是对象是不同的。 你的代码中还有一件事你访问了三个对象,
- 当你调用新的String()。
- 当你分配“你好”。
- 当你分配“帮助!”。
调用新的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的哈希码不会改变。