java什么是不可变的?
这可能是有史以来问过的最愚蠢的问题,但我认为对于一个Java新手来说,这是相当令人困惑的
- 有人能澄清什么是不可变的吗李>
- 为什么
String
是不可变的李> - 不可变对象的优点/缺点是什么李>
- 为什么像
StringBuilder
这样的可变对象应该优先于字符串和反之亦然李>
一个很好的例子(在Java中)将非常感谢
你可以在下面搜索框中键入要查询的问题!
这可能是有史以来问过的最愚蠢的问题,但我认为对于一个Java新手来说,这是相当令人困惑的
String
是不可变的李>
StringBuilder
这样的可变对象应该优先于字符串和反之亦然李>
一个很好的例子(在Java中)将非常感谢
# 1 楼答案
实际上,如果您使用上面建议的wikipedia定义,字符串不是不可变的
字符串的状态在构造后确实会更改。看看hashcode()方法。字符串在本地字段中缓存hashcode值,但直到第一次调用hashcode()时才计算它。这种对hashcode的惰性计算将字符串作为状态发生变化的不可变对象放置在一个有趣的位置,但如果不使用反射,就无法观察到它已经发生了变化
所以不可变的定义应该是一个不能被观察到已经改变的对象
如果不可变对象在创建后状态发生变化,但没有人能看到它(没有反射),那么该对象仍然是不可变的吗
# 2 楼答案
不可变意味着一旦对象的构造函数完成了执行,该实例就不能被更改
这很有用,因为这意味着您可以传递对对象的引用,而不用担心其他人会更改其内容特别是在处理并发性时,永远不会更改的对象没有锁定问题
例如
Foo
不必担心getValue()
的调用方可能会更改字符串中的文本如果您设想一个类似于
Foo
的类,但是成员是StringBuilder
而不是String
,那么您可以看到getValue()
的调用方将能够更改Foo
实例的StringBuilder
属性还要注意你可能发现的不同类型的不变性:Eric Lippert写了一篇关于这一点的文章。基本上,您可以拥有接口不可变但在幕后实际可变的私有状态(因此不能在线程之间安全共享)的对象
# 3 楼答案
“不可变”意味着您不能更改值。如果您有一个String类的实例,那么您调用的任何似乎修改该值的方法实际上都会创建另一个字符串
要保留更改,您应该执行以下操作 foo=foo。sustring(3)
使用集合时,不可变与可变可能会很有趣。考虑一下,如果使用可变对象作为map的键,然后更改值,会发生什么情况(提示:考虑
equals
和hashCode
)# 4 楼答案
不可变对象是不能以编程方式更改的对象。它们特别适用于多线程环境或其他多个进程能够改变(变异)对象中的值的环境
然而,为了澄清,StringBuilder实际上是一个可变的对象,而不是一个不变的对象。常规java字符串是不可变的(这意味着一旦创建了它,就不能在不更改对象的情况下更改基础字符串)
例如,假设我有一个名为ColoredString的类,它有一个字符串值和一个字符串颜色:
在本例中,ColoredString被称为是可变的,因为您可以更改(变异)它的一个关键属性,而无需创建新的ColoredString类。这可能不好的原因是,例如,假设您有一个GUI应用程序,它有多个线程,并且您正在使用彩色字符串将数据打印到窗口。如果您有一个创建为的ColoredString实例
然后,您会期望字符串始终为“蓝色”。但是,如果另一个线程获得该实例并调用
当你想要一根“蓝色”的线时,你会突然,也许是出乎意料地,现在有了一根“红色”的线。因此,在传递对象实例时,几乎总是首选不可变对象。如果确实需要可变对象,则通常只通过从特定的控制域传递副本来保护对象
用Java来概括一下,Java。String是一个不可变的对象(它一旦创建就不能更改)和java。StringBuilder是一个可变对象,因为它可以在不创建新实例的情况下进行更改
# 5 楼答案
不可变对象是无法更改内部字段(或至少影响其外部行为的所有内部字段)的对象
不可变字符串有很多优点:
性能:执行以下操作:
substring()方法的底层C可能是这样的:
请注意,无需复制任何字符如果字符串对象是可变的(字符可能稍后更改),则必须复制所有字符,否则子字符串中字符的更改将在稍后反映在另一个字符串中
并发性:如果不可变对象的内部结构有效,它将始终有效。不同的线程不可能在该对象中创建无效状态。因此,不可变对象是线程安全的
垃圾收集:垃圾收集器更容易对不可变对象做出逻辑决策
然而,不变性也有缺点:
性能:等等,我以为你说性能是不变性的一个优势!嗯,有时是这样,但不总是这样。以下面的代码为例:
这两行都将第四个字符替换为字母“a”。第二段代码不仅可读性更强,而且速度更快。看看您必须如何为foo编写底层代码。子字符串很简单,但是现在因为在第五空间已经有一个字符,而其他的东西可能引用了foo,所以你不能仅仅改变它;您必须复制整个字符串(当然,其中一些功能被抽象为实际底层C中的函数,但这里的重点是显示在一个地方执行的代码)
注意,concatenate被调用两次,这意味着整个字符串必须循环!将其与
bar
操作的C代码进行比较:易变字符串操作显然要快得多
总之:在大多数情况下,您需要一个不可变的字符串。但是,如果需要在字符串中进行大量的追加和插入,则需要速度的可变性。如果您想获得并发安全和垃圾收集的好处,关键是使可变对象保持在方法的本地:
因为
mutable
对象是一个本地引用,所以您不必担心并发安全性(只有一个线程接触过它)。由于它不在其他任何地方引用,因此它只在堆栈上分配,因此函数调用完成后它就会被释放(您不必担心垃圾收集)。您可以获得可变性和不可变性的所有性能优势# 6 楼答案
String s1=“旧字符串”
字符串s2=s1
s1=“新字符串”