这个问题在这里已有答案:
我在不可变字符串上编写了以下代码 .
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-->a
a 2-->ty
这里变量 a
的值已经改变(许多人说不可变对象的内容不能改变) . 但究竟是什么意思说 String is immutable ?你能帮我澄清这个话题吗?
来源:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
19 回答
immutable意味着您不能更改相同referance的值 . 每次创建新referance所需的时间都意味着新的内存位置 . 例如:
这里,在上面的代码中,在存储器中有2个用于存储值的块 . 第一个用于值“abc”,第二个用于“bcd” . 第二个值不代替第一个值 .
这是不可变的 .
在您的示例中,变量
a
只是对字符串对象实例的引用 . 当您说a = "ty"
时,您实际上并没有更改字符串对象,而是将引用指向字符串类的完全不同的实例 .在继续进行不变性的大惊小怪之前,让我们先看一下
String
类及其功能,然后再得出任何结论 .String
的工作原理如下:像往常一样,这会创建一个包含
"knowledge"
的字符串,并为其指定一个引用str
. 够简单吗?让我们执行更多功能:让我们看看以下声明如何工作:
这会将字符串
" base"
附加到str
. 但等等,这怎么可能,因为String
对象是不可变的?令你惊讶的是,它是 .执行上述语句时,VM将获取
String str
的值,即"knowledge"
并附加" base"
,为我们提供值"knowledge base"
. 现在,由于String
是不可变的,因此VM无法将此值分配给str
,因此它会创建一个新的String
对象,为其赋值"knowledge base"
,并为其提供引用str
.这里需要注意的一点是,尽管
String
对象是不可变的, its reference variable is not. 这就是为什么在上面的例子中,引用是为了引用一个新形成的String
对象 .在上面的例子中,我们有两个
String
对象:我们创建的第一个对象"knowledge"
,由s
指向,第二个"knowledge base"
,由str
指向 . 但是,从技术上讲,我们有三个String
对象,第三个是concat
语句中的文字"base"
.关于字符串和内存使用的重要事实
如果我们没有另一个参考
s
到"knowledge"
怎么办?我们会失去那个String
. 但是,它仍然存在,但由于没有引用而被视为丢失 . 再看下面的一个例子What's happening:
第一行非常简单:创建一个新的
String
"java"
并将s1
引用到它 .接下来,VM创建另一个新的
String
"java rules"
,但没有任何内容引用它 . 所以,第二个String
立即丢失 . 我们无法达到它 .引用变量
s1
仍然引用原始String
"java"
.几乎每个应用于
String
对象以修改它的方法都会创建新的String
对象 . 那么,这些String
对象去哪儿了?嗯,这些存在于内存中,任何编程语言的关键目标之一就是有效利用内存 .随着应用程序的增长,
String
文字占用大面积内存非常常见,甚至可能导致冗余 . 所以,为了提高Java的效率, the JVM sets aside a special area of memory called the "String constant pool".当编译器看到
String
literal时,它会在池中查找String
. 如果找到匹配项,则对新文本的引用将定向到现有String
,并且不会创建新的String
对象 . 现有的String
只有一个参考 . 这就是使String
对象不可变的要点:在
String
常量池中,String
对象可能具有一个或多个引用 . 如果多个引用指向相同的String
甚至不知道它,那么如果其中一个引用修改了String
值就不好了 . 这就是为什么String
对象是不可变的 .那么,现在你可以说,如果有人重写
String
类的功能会怎么样?这就是为什么没有人可以覆盖其方法的行为的原因 the String class is marked final .字符串是不可变的意味着您不能更改对象本身,但您可以更改对象的引用 .
当您执行
a = "ty"
时,实际上是将a
的引用更改为由字符串文字"ty"
创建的新对象 .更改对象意味着使用其方法更改其中一个字段(或者字段是公共字段而不是最终字段,以便可以从外部更新它们而无需通过方法访问它们),例如:
在一个不可变的类中(声明为final,以防止通过继承进行修改)(它的方法不能修改它的字段,而且字段总是私有的并且建议为final),例如String,你不能改变当前的String但是你可以返回一个新的String,即:
你正在改变
a
指的是什么 . 试试这个:您将看到
a
然后b
引用的对象未更改 .如果要阻止代码更改
a
引用的对象,请尝试:一个字符串是
char[]
包含一系列UTF-16 code units,该数组的int
偏移量和int
长度 .例如 .
它为字符串引用创建空间 . 分配副本引用,但不修改这些引用引用的对象 .
你也应该意识到这一点
并没有真正做任何有用的事情 . 它只是创建另一个由相同数组,偏移量和长度支持的实例
s
. 很少有理由这样做,因此大多数Java程序员认为这是不好的做法 .像
"my string"
这样的Java双引号字符串实际上是对internedString
实例的引用所以"bar"
是对同一个String实例的引用,无论它在代码中显示多少次 ."hello"创建一个池化的实例,
new String(...)
创建一个非池化的实例 . 试试System.out.println(("hello" == "hello") + "," + (new String("hello") == "hello") + "," + (new String("hello") == new String("hello")));
你应该看到true,false,false
看这里
输出:
这表明无论何时修改不可变字符串对象
a
的内容,都将创建一个新对象 . 即不允许更改不可变对象的内容 . 这就是为什么对象的地址都不同的原因 .您没有更改赋值语句中的对象,而是将一个不可变对象替换为另一个 . 对象
String("a")
不会更改为String("ty")
,它会被丢弃,并且对ty
的引用将被写入a
.相反,
StringBuffer
代表一个可变对象 . 你可以这样做:在这里,您没有重新分配
b
:它仍然指向同一个对象,但该对象的内容已更改 .您实际上是获取对新字符串的引用,字符串本身不会被更改,因为它是不可变的 . 这是相关的 .
看到
Immutable objects on Wikipedia
不可变对象是一个对象,其状态在创建后无法修改 .
所以
a = "ABC"
< - 不可变对象 . "a"保存对象的引用 . 并且,a = "DEF"
< - 另一个不可变对象,"a"现在引用它 .分配字符串对象后,无法在内存中更改该对象 .
总之,您所做的是将“a”的引用更改为新的字符串对象 .
这表明一旦创建了一个字符串对象,就无法更改它 . EveryTime你需要创建新的并放入另一个String . 小号
我认为以下代码清除了差异:
Java
String
是不可变的,String
将以对象的形式存储值 . 因此,如果您指定值String a="a";
,它将创建一个对象,并且该值存储在该对象中并且如果您要分配值a="ty"
意味着它将创建另一个对象存储该值,如果您想要清楚地理解,请检查has code
为String
.只有参考正在改变 . 首先
a
引用了字符串"a",稍后您将其更改为"ty" . 字符串"a"保持不变 .在您的示例中,
a
首先引用"a"
,然后引用"ty"
. 你没有改变任何String
实例;你只是在改变String
实例a
指的是哪个 . 例如,这个:打印"a",因为我们永远不会改变
b
指向的String
实例 .如果某个对象
bar
拥有对可变对象foo
的引用并将其某些状态封装在foo
状态的可变方面,那么将允许可以改变foo
的那些方面的代码来改变bar
状态的相应方面而不实际触摸bar
甚至知道它的存在 . 通常,这意味着使用可变对象封装其自身状态的对象必须确保不会将对这些对象的引用暴露给可能会意外地改变它们的任何代码 . 相比之下,如果bar
持有对象moo
的引用并且仅使用moo
以外的不可变方面来封装其状态,那么bar
可以自由地将moo
暴露给外部代码而不用担心外部代码可能对它做什么 .希望以下代码能澄清您的疑虑:
在String Concat之前:你好
在String Concat之后:你好
在StringBuffer追加之前:你好
StringBuffer追加后:HelloWorld
可能上面提供的每个答案都是正确的,但我的答案是特定于使用
hashCode()
方法,来证明点,如...一旦创建无法修改和修改将导致在不同的内存位置的新值 .OUTPUT
字符串是不可变的,这意味着,一旦创建了String对象的内容就无法更改 . 如果要修改内容,则可以使用StringBuffer / StringBuilder而不是String . StringBuffer和StringBuilder是可变类 .