不可变到底是什么意思——也就是说,对象可变或不可变的结果是什么?特别是,为什么Java的字符串是不可变的?
我的理解是StringBuilder类型类似于String的可变等价。什么时候我会使用StringBuilder而不是字符串,反之亦然?
不可变到底是什么意思——也就是说,对象可变或不可变的结果是什么?特别是,为什么Java的字符串是不可变的?
我的理解是StringBuilder类型类似于String的可变等价。什么时候我会使用StringBuilder而不是字符串,反之亦然?
当前回答
不可变对象是内部字段(或者至少是影响其外部行为的所有内部字段)不能被更改的对象。
不可变字符串有很多优点:
性能:执行如下操作:
String substring = fullstring.substring(x,y);
substring()方法的底层C可能是这样的:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
注意,没有一个字符必须被复制!如果String对象是可变的(字符可以在以后更改),那么您将不得不复制所有字符,否则对子字符串中的字符的更改将在以后反映到另一个字符串中。
并发性:如果一个不可变对象的内部结构是有效的,那么它将总是有效的。不同的线程不可能在该对象中创建无效状态。因此,不可变对象是线程安全的。
垃圾收集:垃圾收集器更容易对不可变对象做出逻辑决策。
然而,不可变性也有缺点:
性能:等等,我记得你说过性能是不变性的好处!有时候是这样,但不总是这样。取以下代码:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
The two lines both replace the fourth character with the letter "a". Not only is the second piece of code more readable, it's faster. Look at how you would have to do the underlying code for foo. The substrings are easy, but now because there's already a character at space five and something else might be referencing foo, you can't just change it; you have to copy the whole string (of course some of this functionality is abstracted into functions in the real underlying C, but the point here is to show the code that gets executed all in one place).
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
注意,concatenate被调用两次,这意味着整个字符串必须循环!将其与bar操作的C代码进行比较:
bar->characters[4] = 'a';
可变字符串操作显然要快得多。
总结:在大多数情况下,您需要一个不可变的字符串。但是如果你需要在一个字符串中做大量的追加和插入,你就需要可变性来提高速度。如果你想要并发安全性和垃圾收集的好处,关键是保持你的可变对象本地的方法:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
因为可变对象是一个本地引用,所以不必担心并发安全性(只有一个线程接触过它)。由于它没有在其他任何地方被引用,所以它只在堆栈上分配,所以函数调用一结束它就会被释放(您不必担心垃圾收集)。你可以同时获得可变性和不可变性的性能优势。
其他回答
“不可变”意味着你不能改变值。如果你有一个String类的实例,你调用的任何方法,只要看起来是在修改这个值,实际上都会创建另一个String。
String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"
要保存更改,您应该这样做 Foo = Foo .sustring(3);
当您使用集合时,不可变与可变可能会很有趣。想想如果使用可变对象作为map的键,然后更改值会发生什么(提示:考虑equals和hashCode)。
String s1="Hi";
String s2=s1;
s1="Bye";
System.out.println(s2); //Hi (if String was mutable output would be: Bye)
System.out.println(s1); //Bye
s1="Hi":创建一个包含"Hi"值的对象s1。
S2 =s1:引用s1对象创建对象S2。
s1="Bye":之前的s1对象的值不会改变,因为s1有String类型,而String类型是一个不可变的类型,相反,编译器创建了一个新的String对象与"Bye"值和s1引用它。在这里,当我们打印s2值时,结果将是“Hi”而不是“Bye”,因为s2引用了前一个具有“Hi”值的s1对象。
不可变意味着一旦一个对象的构造函数完成执行,该实例就不能被改变。
这很有用,因为这意味着你可以传递对对象的引用,而不用担心其他人会改变它的内容。特别是在处理并发性时,对于永不更改的对象不存在锁定问题
e.g.
class Foo
{
private final String myvar;
public Foo(final String initialValue)
{
this.myvar = initialValue;
}
public String getValue()
{
return this.myvar;
}
}
Foo不必担心getValue()的调用者可能会更改字符串中的文本。
如果你想象一个类似于Foo的类,但是成员是StringBuilder而不是String,你可以看到getValue()的调用者能够改变Foo实例的StringBuilder属性。
还要注意你可能会发现的不同类型的不变性:Eric Lippert写了一篇关于这个的博客文章。基本上,你可以拥有接口是不可变的对象,但在幕后实际可变的私有状态(因此不能在线程之间安全地共享)。
其中一个含义与值如何存储在计算机中有关,例如,对于一个。net字符串,它意味着内存中的字符串不能被更改,当你认为你正在更改它时,你实际上是在内存中创建了一个新的字符串,并将现有的变量(这只是一个指向其他地方的实际字符集合的指针)指向新的字符串。
不可变的对象在创建后不能改变其状态。
尽可能使用不可变对象有三个主要原因,所有这些都将有助于减少你在代码中引入的错误数量:
It is much easier to reason about how your program works when you know that an object's state cannot be changed by another method Immutable objects are automatically thread safe (assuming they are published safely) so will never be the cause of those hard-to-pin-down multithreading bugs Immutable objects will always have the same Hash code, so they can be used as the keys in a HashMap (or similar). If the hash code of an element in a hash table was to change, the table entry would then effectively be lost, since attempts to find it in the table would end up looking in the wrong place. This is the main reason that String objects are immutable - they are frequently used as HashMap keys.
当你知道一个对象的状态是不可变的时,你还可以在代码中做一些其他的优化——例如缓存计算的哈希——但这些都是优化,因此没有那么有趣。