什么是不可改变的?

这可能是有史以来最愚蠢的问题,但我认为这是一个新手完全混淆。

  1. 有人可以澄清什么是不可改变的意思吗?
  2. 为什么一个String不可变?
  3. 不可变对象有什么优点/缺点?
  4. 为什么像StringBuilder这样的可变对象比String和副诗更受欢迎呢?

一个很好的例子(在Java中)将非常感激。

不可变意味着一旦对象的构造函数完成执行,那个实例就不能被修改。

这是有用的,因为这意味着你可以传递对象的引用,而不用担心别人会改变它的内容。 特别是在处理并发时,对于永不改变的对象没有locking问题

例如

 class Foo { private final String myvar; public Foo(final String initialValue) { this.myvar = initialValue; } public String getValue() { return this.myvar; } } 

Foo不必担心调用方getValue()可能会更改string中的文本。

如果你想象一个与Foo类似的类,但是使用StringBuilder而不是String作为成员,你可以看到getValue()的调用者将能够改变Foo实例的StringBuilder属性。

还要小心你可能会发现的不同种类的不变性:Eric Lippert写了一篇关于这个的博客文章 。 基本上你可以有对象的接口是不可变的,但在幕后实际可变的私有状态(因此不能在线程之间安全地共享)。

不可变的对象是内部字段(或者至less是影响其外部行为的所有内部字段)不能被改变的对象。

不可变string有很多优点:

性能:采取以下操作:

 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对象是可变的(字符可以稍后改变),那么你将不得不复制所有的字符,否则在子string中的字符改变将反映在另一个string。

并发性:如果不可变对象的内部结构是有效的,它将始终有效。 不同的线程不可能在该对象内创build一个无效的状态。 因此,不可变对象是线程安全的

垃圾收集:垃圾收集器更容易做出有关不可变对象的逻辑决策。

然而,不可变性也存在缺点:

表演:等等,我以为你说演出是不变的好处! 那么,有时,但并非总是如此。 采取以下代码:

 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,所以你不能只是改变它。 你必须复制整个string(当然,这些function中的一部分被抽象为真正的底层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)); 

请注意,连接被调用两次意味着整个string必须循环! 将其与bar操作的C代码进行比较:

 bar->characters[4] = 'a'; 

可变string操作显然要快得多。

结论:在大多数情况下,你需要一个不可变的string。 但是如果你需要做很多的附加和插入到一个string,你需要速度的可变性。 如果你想要并发安全和垃圾收集的好处,关键是保持你的可变对象本地的方法:

 // 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对象是本地引用,所以不必担心并发安全性(只有一个线程触及它)。 而且由于它在其他地方没有被引用,所以只能在堆栈上进行分配,所以只要函数调用完成(不必担心垃圾收集),就会释放它。 你可以获得可变性和不变性的所有性能好处。

实际上,如果使用上面提到的维基百科定义,则string不是不可变的。

string的状态确实改变了后期build设。 看看hashcode()方法。 String将hashcode值caching在本地字段中,但在第一次调用hashcode()之前不会计算它。 散列码的这种懒惰评估将String放置在一个有趣的位置,作为一个状态改变的不可变对象,但不能被观察到没有使用reflection就改变了。

所以也许不可变的定义应该是一个无法观察到的变化的对象。

如果状态在创build之后在不可变对象中发生变化,但没有人能够看到它(没有reflection),对象仍然是不可变的?

不可变对象是不能以编程方式更改的对象。 对于multithreading环境或其他多个进程能够改变(改变)对象中的值的其他环境来说,它们特别有用。

只是为了澄清,然而,StringBuilder实际上是一个可变的对象,而不是一个不可变的对象。 一个普通的javastring是不可变的(这意味着一旦创build了它,就不能在不改变对象的情况下更改基础string)。

例如,假设我有一个名为ColoredString的类,它具有string值和string颜色:

 public class ColoredString { private String color; private String string; public ColoredString(String color, String string) { this.color = color; this.string = string; } public String getColor() { return this.color; } public String getString() { return this.string; } public void setColor(String newColor) { this.color = newColor; } } 

在这个例子中,ColoredString被认为是可变的,因为你可以改变(改变)它的一个关键属性而不需要创build一个新的ColorString类。 这可能是不好的原因是,例如,假设您有一个具有多个线程的GUI应用程序,并且您正在使用ColoredStrings将数据打印到窗口。 如果您有一个创build为的ColoredString的实例

 new ColoredString("Blue", "This is a blue string!"); 

那么你会希望string总是“蓝色”。 如果另一个线程,然而,这个实例的地方,并呼吁

 blueString.setColor("Red"); 

当你想要一个“蓝色”的时候,你会突然,也许意外地,现在有一个“红色”的string。 因此,在传递对象实例时,不可变对象几乎总是首选。 当你有一个可变对象是真的需要的情况下,那么你通常只是通过从你的特定控制领域传出拷贝来保护对象。

回顾一下,在Java中,java.lang.String是一个不可变的对象(一旦创build它就不能改变),java.lang.StringBuilder是一个可变对象,因为它可以在不创build新实例的情况下进行更改。

  1. 在大型应用程序中,常见的string文字占用大量内存。 所以为了高效地处理内存,JVM分配了一个名为“String constant pool”的区域( 注意在内存中,即使是一个未引用的String也携带一个char [],其长度为int,另一个为hashCode。相反,最多需要八个即时字节 )
  2. 当编译器遇到一个string文字时,它会检查这个文件池,看看是否有相同的文字已经存在。 如果find一个,那么对新文字的引用将被引导到现有的string,并且不会创build新的“string字面值对象”(现有string只是简单地获取一个附加引用)。
  3. 因此: string可变性保存内存…
  4. 但是,当任何variables改变值,实际上 – 只是他们的参考被改变,而不是内存中的值(因此它不会影响引用它的其他variables),如下所示….

strings1 =“旧string”;

 //s1 variable, refers to string in memory reference | MEMORY | variables | | [s1] --------------->| "Old String" | 

strings2 = s1;

 //s2 refers to same string as s1 | | [s1] --------------->| "Old String" | [s2] ------------------------^ 

s1 =“新string”;

 //s1 deletes reference to old string and points to the newly created one [s1] -----|--------->| "New String" | | | | |~~~~~~~~~X| "Old String" | [s2] ------------------------^ 

内存中的原始string没有改变,但是引用variables被改变了,所以引用了新的string。 如果我们没有s2,“Old String”仍然会在内存中,但是我们将无法访问它…

“不变”意味着你不能改变价值。 如果你有一个String类的实例,你调用的任何方法似乎都会修改这个值,但实际上会创build另一个String。

 String foo = "Hello"; foo.substring(3); <-- foo here still has the same value "Hello" 

要保存更改,你应该做这样的事情foo = foo.sustring(3);

当你使用集合时,不可变与可变可以很有趣。 想想看,如果你使用可变对象作为映射关键字,然后改变它的值(提示:考虑equalshashCode )会发生什么。

我非常喜欢SCJP Sunauthentication程序员Java 5学习指南的解释 。

为了提高Java的内存使用效率,JVM将一个特殊的内存区域称为“string常量池”。 当编译器遇到一个string时,它会检查这个池是否存在一个相同的string。 如果find匹配项,则对新文字的引用将定向到现有的string,并且不会创build新的string字面值对象。

不可改变的对象在创build后不能改变它们的状态。

有三个主要原因可以随时使用不可变对象,所有这些都将有助于减less在代码中引入的错误数量:

  • 当你知道一个对象的状态不能被另一个方法改变时,理解你的程序是如何工作的更容易
  • 不可变的对象是自动线程安全的(假设它们是安全发布的),所以永远不会成为这些难以解决的multithreading错误的原因
  • 不可变对象将始终具有相同的哈希代码,因此它们可以用作HashMap(或类似的)中的键。 如果哈希表中元素的哈希码发生改变,那么表项就会被有效地丢失,因为在表中find它的尝试最终会在错误的地方查找。 这是String对象不可变的主要原因 – 它们经常用作HashMap键。

当你知道一个对象的状态是不可变的时,你也可以在代码中进行一些其他的优化 – 例如caching计算出来的散列,但是这些都是优化,因此不是那么有趣。

java.time

它可能有点迟,但为了理解不可变对象是什么,请考虑下面的新Java 8 Date and Time API( java.time )中的示例 。 正如你可能知道Java 8中的所有date对象是不可变的,所以在下面的例子中

 LocalDate date = LocalDate.of(2014, 3, 18); date.plusYears(2); System.out.println(date); 

输出:

2014年3月18日

因为plusYears(2)返回一个新对象,所以旧date仍然保持不变,因为它是一个不可变的对象,所以它打印与初始date相同的年份。 一旦创build,你不能进一步修改它,datevariables仍然指向它。

因此,该代码示例应该捕获并使用由该调用实例化并返回的新对象plusYears

 LocalDate date = LocalDate.of(2014, 3, 18); LocalDate dateAfterTwoYears = date.plusYears(2); 

date.toString()… 2014-03-18

dateAfterTwoYears.toString()… 2016-03-18

一个含义是如何存储在计算机中的价值,例如.Netstring,这意味着内存中的string不能改变,当你认为你改变它,你实际上是创造一个新的在内存中的string,并指向现有的variables(这只是一个指向其他地方的字符的实际集合的指针)到新的string。

一旦实例化,就不能改变。 考虑一个类的实例可能被用作哈希表或类似的关键。 查看Java最佳实践。

不可变意味着一旦对象被创build,它的成员就不会改变。 String是不可变的,因为你不能改变它的内容。 例如:

 String s1 = " abc "; String s2 = s1.trim(); 

在上面的代码中,strings1没有改变,另一个对象( s2 )是使用s1创build的。

 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" :一个对象s1是用“Hi”值创build的。

s2=s1 :参照s1对象创build对象s2

s1="Bye" :之前的s1对象的值不会改变,因为s1有Stringtypes,而Stringtypes是不可变的types,所以编译器会创build一个带有Bye值的s1对象, s1引用它。 这里当我们打印s2值时,结果将是“Hi”而不是“Bye”,因为s2引用了前一个有“Hi”值的s1对象。

什么是不变的对象?

根据Java参考文档,“一个对象被认为是不可变的,如果它的状态在构造后不能改变”。简单地说,一个不可变的类是一个类,其属性在创build之后不能被修改。 这意味着在创build时,所有的实例数据都被提供并保持不变,直到对象被销毁。

为什么不可变的对象?

不可变类适用于并发应用程序。 根据它们的不变性质,它们有助于将应用程序保持在不被破坏和一致的行为,因为实例的状态不能被改变。

关注

由于不可变性,不可变类很容易devise,开发和使用,因为它们更具抗错性和安全性。 但是由于可重用性的限制,程序员不愿意使用它们。 它也可能会影响对象的创build而不是重用。 然而,使用最新的复杂编译器,创build成本是最小的。 所提供的function和不变性的优点可以根据需要被采用超过可重用性。

这个String append是如何工作的?

使用给定的值创build新的String对象,并将引用更新为隔离旧对象的新实例。

参考: http : //www.devdummy.com/2017/09/immutable-objects-in-java.html

不可变的对象

一个对象被认为是不可变的,如果它的状态在构造后不能改变的话。 最大程度地依赖不可变对象被广泛接受为创build简单可靠代码的合理策略。

不可变对象在并发应用程序中特别有用。 由于它们不能改变状态,所以不能被线程干扰破坏,或者在不一致的状态下观察。

程序员通常不愿意使用不可变的对象,因为他们担心创build新对象的代价,而不是更新对象。 对象创build的影响常常被高估,并可能被一些与不可变对象相关的效率所抵消。 这些包括由于垃圾收集而减less的开销,以及消除保护可变对象免受破坏所需的代码。

下面的小节将采用一个实例是可变的类,并从中派生一个具有不可变实例的类。 这样做,他们给这种转换的一般规则,并展示一些不可变对象的优点。

资源

一个不可变的对象是你创build后不能修改的对象。 一个典型的例子是string文字。

AD程序devise语言越来越stream行,通过“不变”关键字具有“不可变性”的概念。 查看Dr.Dobb的文章 – http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29 。 它完美地解释了这个问题。