当我们说“整体是不可变的”时,我们究竟是什么意思

时间:2013-04-09 23:41:10

标签: java c++

我对“不可变”的概念感到困惑。我们的教授说“每次都是不可改变的!字符串是不可变的”,他到底意味着什么呢?

一个更一般的问题,我们如何知道数据结构是否是不可变的?

由于

9 个答案:

答案 0 :(得分:7)

这里的一些其他答案是将可变性/不变性与值/引用语义混淆,所以要小心......


简单地说,如果一个实体在创建之后可能被修改,那么它就是可变的。换句话说,它的价值可能会随着时间而改变。

首先,一个反例。 Java String对象是不可变的;没有方法可以调用String对象来改变它的值:

String a = "foo";
a.concat("bar");
System.out.println(a);  // foo

你可以这样做:

String a = "foo";
a = a.concat("bar");
System.out.println(a);  // foobar

但这可行,因为concat()正在创建 String对象,然后会在其中重新引用引用a。现在有两个 String个对象;原来没有改变(它只是永远失去了)。 a是可变的,底层对象不是。


至于int变量;在C或Java中,我们可以这样做:

int x = 3;
x = 4;  // Mutates x
x++;    // Mutates x

我们怎么知道这些真正改变x,而不是简单地创建一个新的整数“对象”和“重新分配”x呢? (除了语言确保原始类型与对象类型不同之外的事实。)在C中,我们可以在某种程度上证明它:

int x = 3;
int *p = x;   // Pointer to original entity
x = 4;
printf("%d\n", *p);   // 4

AFAIK,Java中没有等效的方法。所以你可以争辩说整数类型在Java中是否真正可变是一个无关紧要的问题。


至于我们如何知道给定类型是否是不可变的,我们通常不知道。至少,不是没有检查,或只是相信我们被告知的承诺。

在Java中,确保用户定义的类型是不可变的涉及遵循一些简单的规则(解释here)。但它仍然只是一个承诺;语言没有强制执行。

答案 1 :(得分:3)

不可变性(对象或值,而不是变量)通常意味着无法对值进行就地更改。 (一个会传播到其他引用。)这意味着如果您有类似以下内容:

String a = "foo";

您无法在a上执行可以更改其值的操作。即你不能有一个会导致以下行为的假设方法append()

String a = "foo";
a.append("bar"); // a is not reassigned
System.out.println(a); // prints "foobar"

您可以将此与可变对象(如集合)进行对比:

int[] as = new String[] { "foo" };
as[0] = "bar"; // we're changing `as` in-place - not the Strings stored in it
System.out.println(as[0]); // prints "bar"

原始类型不是Java的一个很好的选择,因为你不能多次引用它们,并且没有办法证明突变和重新分配之间的区别。

答案 2 :(得分:1)

什么是不可变的是高度依赖于语言的,但是不可变对象只是一个在创建后无法更改的对象。

这通常意味着:

int x = 4;
x = 5;//not 'allowed'

这可以在语言中看到,其中诸如int之类的基元可以是不可变的(例如像Scala这样的函数语言)。

OOP中的大多数对象实际上是指向内存中某个位置的指针。如果该对象是不可变的,则内存中的位置不能更改其内容。对于Java中的String,我们看到了这种情况:

String a = "Hello"; //points to some memory location, lets say '0x00001'
a = a + " World!"; //points to a new locations, lets say '0x00002'
System.out.println(a);//prints the contents of memory location '0x00002'

在这种情况下,a实际上在第2行之后指向内存中完全不同的位置。这意味着具有不同范围的另一个已交给a的线程将看不到“Hello World” !”而是“你好”:

String a = "Hello";
startThread(a, " Hello!");//starts some thread and passes a to it
startThread(b, " World!");//starts another thread and passes a to it

   ...

public void methodInThread(String a, String b) {
  a = a + b;
  System.out.println(a);
}

这两个线程将输出以下内容,无论它们被调用的顺序如何:

"Hello Hello!" //thread 1
"Hello World!" //thread 2

答案 3 :(得分:1)

谈论int的不变性是很尴尬的,因为改变不是容器的东西的想法对我们大多数人来说没有意义。我们来谈谈字符串。

这是Python中的一个字符串:

s = "abc"

字符串是容器,因为它们包含一些单独的字符:abc。如果我想将第二个字符更改为d,我可以尝试:

s[1] = 'd'

哪个会因TypeError而失败。我们说Python中的字符串是 immutable ,因为没有改变现有字符串的操作。当然有很多操作会执行一些操作并创建一个 new 字符串,但现有的字符串是一成不变的。

这里有几个优点。一个是它允许实习:有时当一个字符串需要分配时(并且由解释器决定),CPython会注意到已经分配了一个相同的字符串并且只重用了相同的{{1}对象。当字符串是不可变的时,这是最简单的 - 否则,你必须对这样的问题做一些事情:

str

Interning在Python和支持运行时反射的类似语言中特别有用:它必须在运行时知道每个函数和方法的名称,并且很多方法具有内置名称,如{{1 (构造函数方法的名称),因此为所有这些相同的名称重用相同的字符串对象可以节省大量浪费的空间。

另一个优势在于语义:您可以安全地将字符串传递给任意函数,而不必担心它们会在您的背后就地更改。功能程序员很欣赏这种东西。

当然,缺点是使用非常大的字符串进行大量工作需要多次重新分配和重建这些大字符串,而不是对其进行小规模编辑。

现在,关于s = "abc" t = "abc" # this reuses the same memory, as an optimization s[0] = "x" # oops! now t has changed, too! s。这不是不变性的例子:

__init__

这根本不涉及实际的物体;它只为变量int指定一个新值。

改为考虑:

x = 3
x = 4

x语法是“切片表示法”,用于替换列表的全部内容。此处,x = [1, 2, 3] y = x x[:] = [4, 5, 6] print y # [4, 5, 6] x[:] =相同列表的两个名称。因此,当您替换x的内容时,您在y中也会看到相同的效果,因为......它们都命名相同的列表。 (这与其他语言的参考变量不同:您可以为xy分配新值,而不会影响另一种。)

用数字来考虑这一点。如果您可以在普通数字上进行如上所述的假设操作,则会发生这种情况:

x

但你不能这样做。您无法更改现有y所代表的数字。所以我们称它们为不可变的。

在Smalltalk中轻松变异x = 3 y = x x[:] = 4 print y # hypothetically, 4

int

这会 3改为4,覆盖以前包含3的内存。如果int被实习(因为它们可以在Python中),这甚至可能意味着在您的源代码中出现3 become: 4 处,就像数字4

在C中,这些区别并不那么有意义,因为变量是固定的内存块而不是Python的瞬态标签。所以当你这样做时:

int

很难肯定地说这是否“改变”了一个int。它确实覆盖了现有的内存,但这也是C变量赋值的工作方式

反正!可变性只是关于您是在改变现有对象还是用新对象替换它。在Python和Java中,你不能改变现有的字符串,也不能“改变”数字,所以我们称它们是不可变的。您可以自由更改列表和数组就地的内容,而无需创建新的内容,因此它们是可变的。

答案 4 :(得分:0)

如果一个对象的状态在构造之后无法改变,则该对象被认为是不可变的。

来源:http://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html

答案 5 :(得分:0)

通常,这意味着您无法调用将更改

的类型(int或其他)的方法

有时人们将值类型称为不可变的

//theres no way for this to be mutable but this is an example of a value type
int a = 5
int b = a;
b=9

a与类型

不同,不会改变
MyClass a = new MyClass
MyClass b = a
b.DoSomething()
//a is now changed

答案 6 :(得分:0)

不可变对象是一些曾经实例化的东西,无法修改。如果必须修改,将创建一个新对象并指向该引用。

并且整体不是一成不变的。

答案 7 :(得分:0)

java中有一些类是不可变的,如String,All Wrapper Class即。整数,浮点数,长期等。

例如:     整数i = 5;     I = 10;     I = 15;

当Integer i = 5时,这里创建一个新的Integer对象,然后在第二个中,i = 10而不是将此值10分配给先前创建的对象,另一个新对象被创建并分配给i,而3rd = 15,再次创建新对象并再次分配给i。

注意:不要将int与Integer混淆。 int是原始类型,Integer是包装类。所有原语都是可变的。

答案 8 :(得分:0)

可变性和不变性的概念仅与代码可以包含引用的内容相关。如果一个人持有某个东西的引用,并且该东西的状态的一些不可变的方面被观察到具有一些值(或状态),那么只要该引用存在,就可以始终观察到该事物状态的那个方面具有相同的值。 (状态)。

Java中的String类型可以合理地描述为不可变,因为具有对字符串的引用并且观察到它包含字符“Hello”的代码可以随时检查它并且将始终观察它包含那些字符。相比之下,Char[]可能会在一瞬间被观察到包含字母“Hello”,但稍后会被观察到包含字母“Jello”。因此,Char[]被认为是可变的。

因为无法在Java中直接引用int,所以可变性和不变性的概念并不适用于该类型。但是,人们可以引用Integer,它们是相关的。观察到具有特定值的任何此类引用将始终具有相同的值。因此,Integer是不可变的。请注意,虽然可变性和不变性的概念并不真正适用于int之类的值类型,但它们确实共享了不可变类型的有用方面:由存储位置(变量,字段或数组元素)表示的状态保证基本类型或不可变类型不会改变,除非用新值或对不同不可变对象的引用覆盖该位置。