string是不可变的。 究竟是什么意思?
我在不可变的string上写了下面的代码。
public class ImmutableStrings { public static void main(String[] args) { testmethod(); } private static void testmethod() { String a = "a"; System.out.println("a 1-->" + a); a = "ty"; System.out.println("a 2-->" + a); } }
输出:
a 1-->aa 2-->ty
这里variablesa
的值已经被改变了(虽然很多人说不可变对象的内容不能改变)。 但是说一个String
意思是不可变的 ? 你能为我澄清这个话题吗?
来源: https : //docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
在进一步讨论不可变性的问题之前 ,我们先来看一下String
类及其function,然后才能得出关于它的不变性的结论。
这是如何工作的String
:
String str = "knowledge";
像往常一样,这将创build一个包含"knowledge"
的string,并为其分配一个引用str
。 够简单? 让我们执行一些更多的function:
String s = str; // assigns a new reference to the same string "knowledge"
让我们看看下面的声明是如何工作的:
str = str.concat(" base");
这附加一个string" base"
str
。 但是等等,这怎么可能,因为String
对象是不可变的? 那么您的意外,这是。
当执行上面的语句时,VM取String str
的值,即"knowledge"
并附加" base"
,给我们赋值"knowledge base"
。 现在,由于String
是不可变的,所以VM不能将这个值赋给str
,所以它创build一个新的String
对象,给它一个值"knowledge base"
,并给它一个引用str
。
这里需要注意的一点是,虽然String
对象是不可变的,但它的引用variables不是。 所以这就是为什么在上面的例子中引用了一个新形成的String
对象。
在上面的例子中,我们有两个String
对象:第一个是我们用s
指向的值"knowledge"
创builds
,第二个是由str
指向的"knowledge base"
。 但是,从技术上讲,我们有三个String
对象,第三个是concat
语句中的"base"
。
关于string和内存使用的重要事实
如果我们没有另外提到"knowledge"
呢? 我们会丢失那个String
。 但是,它仍然会存在,但由于没有引用而被认为是丢失的。 再看下面的例子
String s1 = "java"; s1.concat(" rules"); System.out.println("s1 refers to "+s1); // Yes, s1 still refers to "java"
发生了什么:
- 第一行非常简单:创build一个新的
String
"java"
并将其引用到s1
中。 - 接下来,VM创build另一个新的
String
"java rules"
,但没有任何指向它。 所以,第二个String
会立即丢失。 我们无法达到它。
引用variabless1
仍然指向原始String
"java"
。
几乎每个应用于String
对象以修改它的方法都会创build一个新的String
对象。 那么,这些String
对象在哪里? 那么这些存在于内存中,任何编程语言的关键目标之一就是高效地使用内存。
随着应用程序的增长, String
文字占用大量内存是非常常见的,甚至会导致冗余。 因此,为了使Java效率更高, JVM将一个特殊的内存区域称为“string常量池”。
当编译器看到一个String
文字时,它在池中查找String
。 如果find匹配项,则对新文字的引用将被引导至现有的String
并且不会创build新的String
对象。 现有的String
只是有一个参考。 这里来使得String
对象不可变:
在String
常量池中,一个String
对象可能有一个或多个引用。 如果几个引用指向相同的String
而不知道它,那么如果其中一个引用修改了该String
值,那将是不好的。 这就是为什么String
对象是不可变的。
那么现在你可以说, 如果有人重写了String
类的function呢? 这就是String
类被标记为final
的原因,所以没有人可以重写它的方法的行为。
string是不可变的,意味着你不能改变对象本身,但你可以改变对象的引用。 当你调用a = "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"
你正在改变a
引用 。 尝试这个:
String a="a"; System.out.println("a 1-->"+a); String b=a; a="ty"; System.out.println("a 2-->"+a); System.out.println("b -->"+b);
你会看到a
和b
引用的对象没有改变。
如果要防止代码更改引用的对象,请尝试:
final String a="a";
一个string是一个char[]
包含一系列UTF-16代码单元 ,该数组的一个int
偏移量和一个int
长度。
例如。
String s
它为string引用创build空间。 指定副本引用,但不会修改这些引用引用的对象。
你也应该知道
new String(s)
真的没有什么用处 它只是创build另一个由与s
相同的数组,偏移量和长度的实例。 很less有理由这样做,所以大多数Java程序员都认为这是不好的做法。
像"my string"
这样的Java双引号string实际上是对interned String
实例的引用,所以"bar"
是对同一个String实例的引用,无论它在代码中出现多less次。
“hello”创build一个汇集的实例, new String(...)
创build一个非共用实例。 尝试System.out.println(("hello" == "hello") + "," + (new String("hello") == "hello") + "," + (new String("hello") == new String("hello")));
你应该看到true,false,false
不可变的意思是你不能改变同样的抵抗的价值。每次你需要创造新的参考意味着新的记忆位置。 例如:
String str="abc"; str="bcd";
在这里,在上面的代码中,存储器中有两个块用于存储值,第一个值为“abc”,第二个为“bcd”,第二个值不replace为第一个值。
这是不可改变的。
在你的例子中,variablesa
只是一个string对象实例的引用。 当你说a = "ty"
,实际上并没有改变string对象,而是把引用指向一个完全不同的string类的实例。
你不改变赋值语句中的对象,用另一个replace一个不可变的对象。 对象String("a")
不会更改为String("ty")
,它会被丢弃,并且ty
的引用会被写入。
相反, StringBuffer
表示一个可变对象。 你可以这样做:
StringBuffer b = new StringBuffer("Hello"); System.out.writeln(b); b.append(", world!"); System.out.writeln(b);
在这里,你没有重新分配b
:它仍然指向同一个对象,但是这个对象的内容已经改变了。
你实际上得到一个新的string的引用,string本身并没有被改变,因为它是不可变的。 这是相关的。
看到
维基百科上的不可变对象
一个不可变的对象是一个对象,它的状态在创build后不能被修改。
所以a = "ABC"
< – 不可变对象。 “a”持有对象的引用。 而且, a = "DEF"
< – 另一个不可变对象,“a”现在拥有对它的引用。
一旦你分配一个string对象,该对象不能在内存中被改变。
总之,你所做的是将“a”的引用改为新的string对象。
String S1="abc"; S1.concat("xyz"); System.out.println("S1 is", + S1); String S2=S1.concat("def"); System.out.println("S2 is", + S2);
这表明一旦一个string对象被创build,它就不能被改变。 每次你需要创build新的,并把另一个string。 小号
我认为下面的代码清除了不同之处:
String A = new String("Venugopal"); String B = A; A = A +"mitul"; System.out.println("A is " + A); System.out.println("B is " + B); StringBuffer SA = new StringBuffer("Venugopal"); StringBuffer SB = SA; SA = SA.append("mitul"); System.out.println("SA is " + SA); System.out.println("SB is " + SB);
Java String
是不可变的, String
将以对象的forms存储值。 所以如果你赋值的String a="a";
它会创build一个对象,并将值存储在那个和如果你分配的值再一次,如果你想明白地理解,检查has code
为a="ty"
意味着它将创build另一个对象存储的值, String
。
看这里
class ImmutableStrings { public static void main(String[] args) { testmethod(); } private static void testmethod() { String a="a"; System.out.println("a 1-->"+a); System.out.println("a 1 address-->"+a.hashCode()); a = "ty"; System.out.println("a 2-->"+a); System.out.println("a 2 address-->"+a.hashCode()); } }
输出:
a 1-->a a 1 address-->97 a 2-->ty a 2 address-->3717
这表明,无论何时修改不可变string对象的内容,都会创build一个新对象。 即你不允许改变不可变对象的内容。 这就是为什么地址对于这两个对象都是不同的。
只有参考正在改变。 首先是引用string“a”,后来改为“ty”。 string“a”保持不变。
在你的例子中, a
先引用"a"
,然后引用"ty"
。 你不会改变任何String
实例; 你只是改变引用的String
实例。 例如,这个:
String a = "a"; String b = a; // b refers to the same String as a a = "b"; // a now refers to a different instance System.out.println(b);
打印“a”,因为我们从来不会改变b
指向的String
实例。
如果某个对象bar
持有对可变对象foo
的引用, 并将其状态的某些状态封装在foo
状态的可变方面 ,那么将允许可以更改foo
这些方面的代码来改变bar
状态的对应方面触摸bar
,甚至知道它的存在 。 一般来说,这意味着使用可变对象封装自己状态的对象必须确保对这些对象的引用不会被暴露给任何可能意外使它们变异的代码。 相比之下,如果bar
持有一个对象moo
的引用,并且只使用moo
不可改变的方面来封装它的状态,那么bar
就可以自由地将moo
暴露给外部的代码,而不用担心外部代码可能做的任何事情。
希望下面的代码能够澄清你的疑惑:
public static void testString() { String str = "Hello"; System.out.println("Before String Concat: "+str); str.concat("World"); System.out.println("After String Concat: "+str); StringBuffer sb = new StringBuffer("Hello"); System.out.println("Before StringBuffer Append: "+sb); sb.append("World"); System.out.println("After StringBuffer Append: "+sb); }
之前stringConcat:你好
后stringConcat:你好
在StringBuffer之前附加:Hello
在StringBuffer之后追加:HelloWorld
string是不可变的,这意味着string对象的内容不能被改变
如果你想修改内容使用StringBuffer而不是可变的String
大概上面提供的每个答案都是正确的,但是我的答案是使用hashCode()
方法来certificate像String一样创build的点不能被修改,修改会在不同的内存位置产生新的值。
public class ImmutabilityTest { private String changingRef = "TEST_STRING"; public static void main(String a[]) { ImmutabilityTest dn = new ImmutabilityTest(); System.out.println("ChangingRef for TEST_STRING OLD : " + dn.changingRef.hashCode()); dn.changingRef = "NEW_TEST_STRING"; System.out.println("ChangingRef for NEW_TEST_STRING : " + dn.changingRef.hashCode()); dn.changingRef = "TEST_STRING"; System.out.println("ChangingRef for TEST_STRING BACK : " + dn.changingRef.hashCode()); dn.changingRef = "NEW_TEST_STRING"; System.out.println("ChangingRef for NEW_TEST_STRING BACK : " + dn.changingRef.hashCode()); String str = new String("STRING1"); System.out.println("String Class STRING1 : " + str.hashCode()); str = new String("STRING2"); System.out.println("String Class STRING2 : " + str.hashCode()); str = new String("STRING1"); System.out.println("String Class STRING1 BACK : " + str.hashCode()); str = new String("STRING2"); System.out.println("String Class STRING2 BACK : " + str.hashCode()); } }
OUTPUT
ChangingRef for TEST_STRING OLD : 247540830 ChangingRef for NEW_TEST_STRING : 970356767 ChangingRef for TEST_STRING BACK : 247540830 ChangingRef for NEW_TEST_STRING BACK : 970356767 String Class STRING1 : -1163776448 String Class STRING2 : -1163776447 String Class STRING1 BACK : -1163776448 String Class STRING2 BACK : -1163776447