不可变的意思是什么?

110

如果一个字符串是不可变的,那么这是否意味着...(假设使用JavaScript)

var str = 'foo';

alert(str.substr(1)); // oo

alert(str); // foo

这是否意味着当调用字符串的方法时,它将返回修改后的字符串,但不会改变初始字符串?

如果字符串是可变的,那么第二个alert()也会返回oo吗?

10个回答

107

这意味着,一旦您实例化对象,就无法更改其属性。在第一个警报中,您没有更改foo。您正在创建一个新字符串。这就是为什么在第二个警报中它将显示"foo"而不是"oo"。

这是不是意味着,在字符串上调用方法会返回修改后的字符串,但不会更改初始字符串?

是的。一旦创建了字符串,就不能更改它。现在这并不意味着您不能将一个新的字符串对象分配给str变量。只是您不能更改当前由str引用的对象。

如果字符串是可变的,这是否意味着第二个alert()也会返回"oo"?

从技术上讲,不会,因为substring方法返回一个新字符串。使一个对象可变并不会改变该方法。使其可变意味着从技术上讲,您可以使其如此,以便substring更改原始字符串而不是创建一个新字符串。


同一个字符串如果被修改并赋值,将会更新该字符串。var str = 'foo';str = str.substr(1)); // ooalert(str); // oo - Ashwin G
以下代码怎么样?字符串值已经改变了。var name = "Santosh"; console.log(name.substr(0,2)); var name = "kumar" console.log(name); 它如何仍然是不可变的。 - Santosh

103

在更低层面上,不可变性意味着存储字符串的内存不会被修改。一旦你创建了一个字符串 "foo",会分配一些内存来存储值 "foo",这个内存不会被更改。如果你使用substr(1)修改字符串,那么将创建一个新的字符串,并分配一个不同的内存部分来存储 "oo"。现在你有两个字符串在内存中,"foo""oo"。即使你不再使用 "foo",它也会一直保留,直到被垃圾回收。

这也是字符串操作相对较慢的原因之一。


1
数字也是不可变的。 - RN Kushwaha
3
来自2015年的问候!“在较低的层面,不可变性意味着存储字符串的内存不会被修改。”——这太过严格并且不太正确。不可变性仅与用户空间有关,只要语言规范不变,虚拟内存可以像内存分配器所需一样进行更改。 - zerkms
2
这个答案比被接受的答案更合乎逻辑。 - Ejaz Karim
但是我可以像这样做 var str = 'foo'; 通过 str = 'bar' 进行修改。这样 str 的值就被修改了,对吧? - Hacker
@Hacker 不对。你已经将一个新的字符串分配给了指向内存中新位置的变量。 - deceze

14

Immutable(不可变)意味着不能被改变或修改。

当你给一个字符串赋值时,新的值是从头开始创建的,而不是替换原有的值。所以每次将一个新值赋给相同的字符串时,都会创建一个副本。因此实际上,你从未改变过原始值。


10
我不确定JavaScript,但在Java中,字符串需要额外的步骤来实现不可变性,即使用"String Constant Pool"。可以使用字符串文字(“foo”)或String类构造函数构造字符串。使用字符串文字构造的字符串是String Constant Pool的一部分,并且相同的字符串字面量始终是来自池中的相同内存地址。
示例:
    String lit1 = "foo";
    String lit2 = "foo";
    String cons = new String("foo");

    System.out.println(lit1 == lit2);      // true
    System.out.println(lit1 == cons);      // false

    System.out.println(lit1.equals(cons)); // true
在上面的代码中,lit1lit2都使用相同的字符串字面量构造,所以它们指向相同的内存地址;lit1 == lit2的结果为true,因为它们恰好是同一个对象。
然而,cons是使用类构造函数构造的。尽管参数是相同的字符串常量,但构造函数为cons分配了新的内存,这意味着cons不是与lit1lit2相同的对象,尽管它们包含相同的数据。
当然,由于这三个字符串都包含相同的字符数据,使用equals方法将返回true。
(两种类型的字符串构造都是不可变的,当然)

4

可变性的教科书定义是可以改变或修改的。在编程中,我们使用这个词来表示那些状态允许随时间改变的对象。不可变值则完全相反 - 在它被创建后,它永远不会改变。

如果这看起来很奇怪,让我提醒你,我们经常使用的许多值实际上是不可变的。

var statement = "I am an immutable value";
var otherStr = statement.slice(8, 17);

我认为没人会惊讶地得知第二行代码并没有改变语句中的字符串。实际上,所有字符串方法都不会改变它们所操作的字符串,而是返回新字符串。原因是字符串是不可变的 - 它们无法改变,我们只能创建新字符串。
字符串不是 JavaScript 内置的唯一不可变值。数字也是不可变的。你能想象一个环境,其中计算表达式2 + 3会改变数字2的含义吗?这听起来很荒谬,但是我们经常这样做,对于对象和数组也是如此。

3

Immutable指的是值不能被改变。一旦创建了一个字符串对象,它就是不可修改的。如果您要求一个字符串的子串,将会创建一个具有所请求部分的新字符串。

在操作字符串时使用StringBuffer可以使操作更高效,因为StringBuffer将字符串存储在字符数组中,并使用变量来保存字符数组的容量和长度(以字符数组形式的字符串)。


2
从字符串到堆栈...一个易于理解的例子,摘自Eric Lippert的博客使用C# 3.0中的A*算法进行路径查找,第二部分...
可变的堆栈,如System.Collections.Generic.Stack,显然不适合我们的需求。我们想要从现有路径创建新路径,以便为其最后一个元素的所有邻居创建新路径,但是将新节点推入标准堆栈会修改堆栈。我们必须在推送之前复制堆栈,这很愚蠢,因为这样我们将不必要地复制所有内容。
不可变的堆栈没有这个问题。将节点推入不可变堆栈仅会创建一个全新的堆栈,它链接到旧堆栈作为其尾部。由于堆栈是不可变的,因此没有其他代码会干扰尾部内容。您可以继续使用旧堆栈。
要深入了解不可变性,请阅读Eric的文章,从这篇文章开始: C#中的不可变性,第一部分:不可变性的种类

那个引用包含一个奇怪的声明 - 栈数据结构(实际上是具有尾部共享的多个栈)作为一个整体是可变的结构 - 每次推入或弹出都会改变结构。只有单个项目是不可变的。原则上,您可以拥有相同的结构,但具有可变元素。这在某些可撤销的并查集变体中发生,如果我没记错的话。不可变性具有巨大的优势,但共享部分或全部数据结构作为优化与可变性完全可能。即使您希望其他引用具有表面不可变性,您也可以根据需要进行复制写入。 - user180247

0

理解这个概念的一种方法是看看JavaScript如何处理所有对象,即通过引用。这意味着所有对象在实例化后都是可变的,这意味着您可以添加具有新方法和属性的对象。这很重要,因为如果您希望对象是不可变的,则对象在实例化后不能更改。


0

可变性的教科书定义是易于改变或修改。在编程中,我们使用这个词来表示那些状态允许随时间改变的对象。不可变值则完全相反 - 在创建后,它永远不会改变。

如果这看起来很奇怪,让我提醒你,我们经常使用的许多值实际上都是不可变的。 var statement = "我是一个不可变的值"; var otherStr = statement.slice(8, 17);

我想没有人会感到惊讶,得知第二行代码并没有以任何方式改变语句中的字符串。事实上,没有任何字符串方法会改变它们所操作的字符串,它们都会返回新的字符串。原因是字符串是不可变的 - 它们不能改变,我们只能创建新的字符串。

字符串不是内置于JavaScript中的唯一不可变值。数字也是不可变的。你能想象出一个环境,在这个环境中计算表达式2 + 3会改变数字2的含义吗?这听起来很荒谬,然而我们经常这样做,处理对象和数组。


0

试一下这个:

let string = "name";

string[0] = "N";

console.log(string); // name not Name

string = "Name";

console.log(string); // Name

因此,这意味着字符串是不可变的但不是常量,简单地说,重新赋值是可以发生的,但不能改变某些部分。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接