为什么string不能在Java和.NET中变化?

为什么他们决定在Java和.NET(以及其他一些语言)中使string不可变? 他们为什么不把它变成可变的?

根据Effective Java ,第73页第4章,第2版:

“有很多很好的理由:不可变类比可变类更容易devise,实现和使用,它们不易出错,更安全。

[…]

不可变的对象很简单,一个不可变的对象可以处于一种状态,也就是创build它的状态。如果确保所有的构造函数都build立了类不variables,那么就保证这些不variables在所有的时间都保持为真,没有你的努力。

[…]

不可变对象本质上是线程安全的; 他们不需要同步。 它们不能被多个线程同时访问它们破坏。 这是实现线程安全的最简单方法。 事实上,没有线程可以观察到另一个线程对不可变对象的影响。 因此, 不可变对象可以自由共享

[…]

同一章中的其他小点:

您不仅可以共享不可变的对象,还可以共享其内部结构。

[…]

不可变对象为其他对象提供了很好的构build块,无论是可变的还是不可变的。

[…]

不可变类唯一真正的缺点是它们需要为每个不同的值单独的对象。

至less有两个原因。

首先 – 安全 http://www.javafaq.nu/java-article1060.html

String不变的主要原因是安全性。 看看这个例子:我们有一个文件打开方法与login检查。 我们传递一个string到这个方法来处理在调用被传递给操作系统之前所必需的authentication。 如果String是可变的,那么在操作系统从程序获得请求之前,在validation检查之后可以以某种方式修改其内容,然后可以请求任何文件。 因此,如果您有权在用户目录中打开文本文件,但是在某种程度上您设法更改文件名时可以请求打开“passwd”文件或其他任何文件。 然后可以修改文件,并可以直接login到操作系统。

第二 – 内存效率 http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

JVM在内部维护“string池”。 为了实现内存效率,JVM将从池中引用String对象。 它不会创build新的String对象。 因此,无论何时创build新的string文字,JVM都会检查池中是否已经存在。 如果已经存在于池中,只需给同一对象的引用或在池中创build新对象。 会有很多引用指向相同的String对象,如果有人改变了这个值,它会影响到所有的引用。 所以,太阳决定让它不可改变。

实际上,java中不可变的原因与安全性没有多大关系。 主要有两个原因:

安全性:

string是使用极为广泛的对象types。 因此,它或多或less地保证在multithreading环境中使用。 string是不可变的,以确保在线程之间共享string是安全的。 拥有一个不可变的string可以确保当将string从线程A传递到另一个线程B时,线程B不会意外地修改线程A的string。

这不仅有助于简化已经非常复杂的multithreading编程任务,而且还有助于提高multithreading应用程序的性能。 当可以从多个线程访问可变对象时,必须以某种方式进行同步,以确保一个线程在另一个线程正在修改对象时不会尝试读取对象的值。 适当的同步对于编程人员来说是很难做到的,在运行时也是很昂贵的。 不可变对象不能被修改,因此不需要同步。

性能:

虽然已经提到了string实例,但它只是Java程序的内存效率的一小部分增益。 只有string文字是interned。 这意味着只有源代码中相同的string将共享相同的string对象。 如果你的程序dynamic创build的string是相同的,它们将被表示在不同的对象中。

更重要的是,不可变的string允许他们共享他们的内部数据。 对于许多string操作,这意味着底层的字符数组不需要被复制。 例如,假设你想要采用String的五个第一个字符。 在Java中,你可以调用myString.substring(0,5)。 在这种情况下,substring()方法所做的仅仅是创build一个新的String对象,该对象共享myString的底层char [],但是谁知道它从索引0开始到索引5结束于char []。 要以graphics的formsexpression,你将会得到以下结果:

  | myString | vv "The quick brown fox jumps over the lazy dog" <-- shared char[] ^ ^ | | myString.substring(0,5) 

这使得这种操作非常便宜,并且O(1)由于操作既不取决于原始string的长度,也不取决于我们需要提取的子串的长度。 这种行为也有一些内存好处,因为许多string可以共享它们的底层char []。

线程安全性和性能。 如果一个string不能被修改,那么在多个线程之间传递引用是安全和快速的。 如果string是可变的,你将永远不得不将string的所有字节复制到一个新的实例,或提供同步。 每次需要修改string时,典型的应用程序都会读取一个string100次。 请参阅关于不变性的维基百科。

一个人应该问,“为什么X应该是可变的?” 由于公主绒毛已经提到的好处,最好默认为不变性。 它应该是一个例外,有些东西是可变的。

不幸的是,大多数当前的编程语言默认为可变性,但是希望将来默认更多的是不可变性(参见下一主stream编程语言的愿望清单 )。

一个因素是,如果string是可变的,存储string的对象必须小心地存储副本,以免其内部数据改变,恕不另行通知。 鉴于string是一个相当简单的types,如数字,它是好的时候,可以把它们视为如果他们被传递的价​​值,即使他们通过引用传递(这也有助于节省内存)。

哇! 我不能相信这里的错误信息。 string是不可改变的,没有任何安全性。 如果有人已经可以访问正在运行的应用程序中的对象(如果您试图防止某人在您的应用程序中“窃取”一个string,则必须假定这些对象),那么他们肯定会有大量其他机会可用于黑客行为。

String的不变性解决了线程问题,这是一个非常新颖的想法。 嗯…我有一个对象正在被两个不同的线程改变。 我如何解决这个问题? 同步访问对象? Naawww …让我们不要让任何人改变对象 – 这将解决我们所有的杂乱并发问题! 实际上,让所有的对象都是不可变的,然后我们可以从Java语言中去除同构化的结构。

真正的原因(上面的人指出)是内存优化。 在任何应用程序中重复使用相同的string文字是相当普遍的。 事实上,几十年前,很多编译器对存储string文本的单个实例进行了优化。 这种优化的缺点是,修改string文字的运行时代码引入了一个问题,因为它正在修改所有其他共享代码的实例。 例如,对于某个应用程序中的某个函数来说,将string“dog”更改为“cat”是不好的。 一个printf(“狗”)会导致“猫”被写入标准输出。 出于这个原因,需要有一种方法来防止试图更改string文字的代码(即使它们不可变)。 一些编译器(在OS的支持下)可以通过将string文字放入特殊的只读内存段来实现这一点,如果进行了写入尝试,会导致内存错误。

在Java中这被称为实习。 这里的Java编译器只是遵循数十年编译器所做的标准内存优化。 为了解决在运行时被修改的这些string文字的相同问题,Java只是简单地让String类不可变(即,不给你任何让你改变String内容的setter)。 如果string文字没有出现,string不一定是不可改变的。

string不是一个原始types,但通常你想使用它的价值语义,即像一个值。

价值是你可以信任的东西不会改变你的背后。 如果你写: String str = someExpr(); 你不想让它改变,除非你用str做某些事情。

string作为一个对象具有自然的指针语义,以获得价值的语义,以及它需要是不可变的。

我知道这是一个颠簸,但是…他们真的是不可改变的吗? 考虑以下几点。

 public static unsafe void MutableReplaceIndex(string s, char c, int i) { fixed (char* ptr = s) { *((char*)(ptr + i)) = c; } } 

 string s = "abc"; MutableReplaceIndex(s, '1', 0); MutableReplaceIndex(s, '2', 1); MutableReplaceIndex(s, '3', 2); Console.WriteLine(s); // Prints 1 2 3 

你甚至可以把它作为扩展方法。

 public static class Extensions { public static unsafe void MutableReplaceIndex(this string s, char c, int i) { fixed (char* ptr = s) { *((char*)(ptr + i)) = c; } } } 

这使得以下工作

 s.MutableReplaceIndex('1', 0); s.MutableReplaceIndex('2', 1); s.MutableReplaceIndex('3', 2); 

结论:它们处于编译器已知的不可变状态。 以上只适用于.NETstring,因为Java没有指针。 但是,一个string可以完全使用C#中的指针进行修改。 这不是如何使用指针,有实际使用或安全使用; 这是可能的,从而弯曲整个“可变”的规则。 您通常不能直接修改string的索引,这是唯一的方法。 有一种方法可以通过禁止string指针实例或指向string时进行复制来防止这种情况发生,但是两者都没有完成,这使得C#中的string不是完全不可变的。

对于大多数目的来说,“string”(被认为是/被认为是/被认为是)一个有意义的primefaces单位, 就像一个数字一样

问为什么一个string的单个字符不可变是因为要问为什么一个整数的个别位不可变。

你应该知道为什么。 考虑一下。

我讨厌这么说,但不幸的是,我们正在辩论这个问题,因为我们的语言很糟糕,而且我们试图用单个string来描述一个复杂的,上下文相关的概念或对象类。

我们用“string”进行计算和比较,类似于我们如何处理数字。 如果string(或整数)是可变的,我们将不得不编写特殊的代码来将它们的值locking到不可变的本地表单中,以便可靠地执行任何types的计算。 因此,最好将数字标识符看作string,而不是16位,32位或64位长,可能是数百位长。

当有人说“串”时,我们都想到不同的东西。 那些把它简单地看作是一组没有特别目的的angular色的人,当然会感到震惊的是,有人只是认为他们不应该能够操纵这些angular色。 但“string”类不只是一个字符数组。 这是一个STRING ,而不是char[] 。 对于我们称之为“string”的概念有一些基本的假设,它通常可以被描述为编码数据的有意义的primefaces单位,如数字。 当人们谈论“操纵string”时,也许他们真的在讨论如何操纵字符来构buildstring ,而StringBuilder对此非常有用。 只是想一下“串”这个词的真正意义。

想想看,如果string是可变的,它会是什么样子。 如果可变用户名string被另一个线程有意或无意地修改,而这个函数正在使用它,那么下面的API函数可能会被诱骗到不同用户的返回信息中:

 string GetPersonalInfo( string username, string password ) { string stored_password = DBQuery.GetPasswordFor( username ); if (password == stored_password) { //another thread modifies the mutable 'username' string return DBQuery.GetPersonalInfoFor( username ); } } 

安全不仅仅是“访问控制”,还关系到“安全”和“保证正确性”。 如果一个方法不容易编写,并依赖于可靠地进行简单的计算或比较,那么调用它是不安全的,但是对编程语言本身提出质疑是安全的。

这是一个权衡。 string进入string池,当你创build多​​个相同的string,他们共享相同的内存。 devise师们认为这种节省内存的技术对于常见的情况来说效果不错,因为程序往往会在相同的string上磨碎很多。

不足之处在于,连接会产生大量额外的string,这些string只是过渡性的,只会变成垃圾,实际上会损害内存性能。 在这些情况下,您有StringBuffer和StringBuilder(在Java中,StringBuilder也在.NET中)用于保留内存。

Java中的string并不是真正的不可变的,你可以使用reflection和/或类加载来改变它们的值。 你不应该依靠这个财产来保证安全。 例如见: Magic Trick In Java

不变性与安全密切相关。 至less在.NET中,你得到了SecureString类。

C ++中string可变的决定会带来很多问题,请参阅Kelvin Henney关于疯牛病的优秀文章。

COW =复制写入。

不变性是好的。 请参阅有效的Java。 如果每次传递一个string都需要复制一个string,那么这将会是很多容易出错的代码。 你也有困惑哪些修改影响哪些参考。 与Integer必须是不可变的行为类似于int一样,Strings必须像不变的行为一样行为。 在C ++中按值传递string在源代码中没有明确提及。

这主要是出于安全原因。 如果您不能相信您的string是防篡改的,那么保护系统就更困难了。

几乎每个规则都有一个例外:

 using System; using System.Runtime.InteropServices; namespace Guess { class Program { static void Main(string[] args) { const string str = "ABC"; Console.WriteLine(str); Console.WriteLine(str.GetHashCode()); var handle = GCHandle.Alloc(str, GCHandleType.Pinned); try { Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z'); Console.WriteLine(str); Console.WriteLine(str.GetHashCode()); } finally { handle.Free(); } } } } 

string是不可变的(一旦创build了不能改变的)对象。 创build为String的对象存储在常量string池中。 Java中的每个不可变对象都是线程安全的,这意味着String也是线程安全的.String不能被两个线程同时使用。 曾经分配过的string不能改变。

 String demo = " hello " ; 

//上面的对象存储在常量string池中,其值不能修改。

 demo="Bye" ; 

//新的“Bye”string在常量池中创build,并由演示variables引用

//“hello”string仍然存在于string常量池中,其值不会被覆盖,但是我们失去了对“hello”string的引用