什么是不可改变的?
这可能是有史以来最愚蠢的问题,但我认为对于新手来说这是一个完全混淆的问题。
String
不可变? StringBuilder
这样的可变对象比String和副诗更受欢迎? 一个很好的例子(在Java中)将非常感激。
不可变意味着一旦对象的构造函数完成执行,那个实例就不能被修改。
这很有用,因为它意味着你可以传递对象的引用,而不用担心别人会改变它的内容。 特别是在处理并发时,对于永远不会改变的对象没有锁定问题
例如
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写了一篇关于此的博客文章。 基本上你可以有对象的接口是不可变的,但在后台实际可变的私有状态(因此不能在线程之间安全地共享)。
一个不可变对象是一个内部字段(或者至少是影响其外部行为的所有内部字段)不能被改变的对象。
不可变字符串有很多优点:
性能:采取以下操作:
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
这两行都用字母“a”代替第四个字符。 不仅第二块代码更具可读性,速度更快。 看看你将如何做foo的底层代码。 子串很容易,但是现在因为已经有一个字符在空间五,而另一些可能引用了foo,所以你不能只是改变它; 你必须复制整个字符串(当然这些功能中的一部分被抽象为真正的底层C中的函数,但这里的重点是要显示在一个地方执行的代码)。
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();
}
由于mutable
对象是本地引用,因此不必担心并发安全性(只有一个线程触及它)。 由于它在其他地方没有被引用,因此它只在堆栈中分配,所以只要函数调用完成(您不必担心垃圾收集),它就会被释放。 你可以获得可变性和不变性的所有性能优势。
实际上,如果您使用上面提到的维基百科定义,则字符串不是不可变的。
字符串的状态确实会改变后期制作。 看看hashcode()方法。 字符串将哈希码值存储在本地字段中,但在第一次调用hashcode()之前不计算它。 对散列码的懒惰评估将String放置在一个有趣的位置,作为一个状态发生改变的不可变对象,但是如果不使用反射,它就不能被观察到。
所以也许不可变的定义应该是一个无法观察到的变化的对象。
如果状态在创建后的不可变对象中发生变化,但没有人能够看到它(没有反射),对象仍然是不可变的?
链接地址: http://www.djcxy.com/p/73629.html