我对“不可变”的概念感到困惑。我们的教授每天都在说“整数是不可变的!字符串是不可变的”,他到底是什么意思?
一个更普遍的问题,我们如何知道数据结构是否不可变?
谢谢
这里的其他一些答案将可变性/不变性与值/引用语义混淆了,所以要小心......
简而言之,如果实体在创建后可以修改,则它是可变的。换句话说,它的价值可能会随着时间而改变。
首先,一个反例。JavaString
对象是不可变的;没有可以调用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
我们如何知道这些真的 mutate x
,而不是简单地创建一个新的整数“对象”并“重新指向”x
它?(除了语言向我们保证原始类型不同于对象类型这一事实。)在 C 中,我们可以在某种程度上证明这一点:
int x = 3;
int *p = x; // Pointer to original entity
x = 4;
printf("%d\n", *p); // 4
AFAIK,Java 中没有等效的方法。因此,您可能会争辩说整数类型在 Java 中是否真正可变的问题是无关紧要的。
至于我们如何知道给定类型是否不可变,我们通常不知道。至少,不是没有检查它,或者只是相信我们被告知的一个承诺。
在 Java 中,确保用户定义的类型是不可变的涉及遵循一些简单的规则(在此处解释)。但这仍然只是一个承诺;语言不强制执行它。
不变性(对象或值,而不是变量)通常意味着无法对值进行就地更改。(一个会传播到其他对它的引用。)这意味着如果您有以下内容:
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 示例的一个很好的选择,因为您不能对它们有多个引用,并且没有办法证明突变和重新分配之间的区别。
谈论int
s 的不变性很尴尬,因为改变不是容器的东西的想法对我们大多数人来说没有意义。那么让我们来谈谈字符串。
这是一个字符串,在 Python 中:
s = "abc"
字符串是容器,因为它们包含一定数量的单个字符:here a
、b
和c
. 如果我想将第二个字符更改为 a d
,我可以尝试:
s[1] = 'd'
哪个会失败TypeError
。我们说字符串在 Python 中是不可变的,因为没有任何操作会改变现有的字符串。当然有很多操作会执行一些操作并创建一个新的字符串,但是现有的字符串是一成不变的。
这里有几个优点。一个是它允许实习:有时当一个字符串需要分配时(并且由解释器决定),CPython 会注意到一个相同的字符串已经被分配并且只是重用相同的str
对象。当字符串是不可变的时,这是最简单的——否则,你必须对这样的问题做一些事情:
s = "abc"
t = "abc" # this reuses the same memory, as an optimization
s[0] = "x" # oops! now t has changed, too!
实习在 Python 和支持运行时反射的类似语言中特别有用:它必须在运行时知道每个函数和方法的名称,并且很多方法都有内置名称,例如__init__
(构造函数方法的名称),因此重用相同的所有这些相同名称的字符串对象可以节省大量浪费的空间。
另一个优势在于语义:您可以安全地将字符串传递给任意函数,而不必担心它们会在您背后就地更改。函数式程序员很欣赏这种东西。
当然,缺点是对非常大的字符串进行大量工作需要多次重新分配和重建这些大字符串,而不是在原地进行小的编辑。
现在,关于int
s。这不是不变性的示例:
x = 3
x = 4
这根本不涉及实际对象;它只为变量分配一个新值x
。
改为考虑:
x = [1, 2, 3]
y = x
x[:] = [4, 5, 6]
print y # [4, 5, 6]
语法是“x[:] =
切片表示法”,用于替换列表的全部内容。在这里,x
和y
是同一个列表的两个名称。因此,当您替换 的内容时x
,您也会在 中看到相同的效果y
,因为...它们都命名了相同的列表。(这与其他语言中的引用变量不同:您可以为其中一个x
或y
不影响另一个分配一个新值。)
用数字来考虑这一点。如果您可以对普通数字进行上述假设操作,则会发生这种情况:
x = 3
y = x
x[:] = 4
print y # hypothetically, 4
但你不能那样做。您无法更改现有int
代表的数字。所以我们称它们为不可变的。
int
在 Smalltalk 中改变 an很容易:
3 become: 4
这会将3更改为 4,覆盖之前包含 3 的内存。如果int
s 被实习(因为它们可以在 Python 中),这甚至可能意味着3
源代码中到处都出现,它就像数字 4 一样。
在 C 中,这些区别没有那么有意义,因为变量是固定的内存块,而不是 Python 的临时标签。所以当你这样做时:
int x = 3;
x = 4;
很难明确地说这是否是在“变异”一个 int。它确实会覆盖现有内存,但这也是 C 变量赋值的工作方式。
反正!可变性只是关于您是要更改现有对象还是用新对象替换它。在 Python 和 Java 中,您无法更改现有字符串,也无法“更改”数字,因此我们称它们为不可变的。您可以就地更改列表和数组的内容而无需创建新内容,因此它们是可变的。
不可变是高度依赖于语言的,但不可变对象只是一个创建后无法更改的对象。
这通常意味着:
int x = 4;
x = 5;//not 'allowed'
这在诸如 int 之类的原语可以是不可变的语言(例如 Scala 之类的函数式语言)中可以看到。
OOP 中的大多数对象实际上是指向内存中某个位置的指针。如果该对象是不可变的,则内存中的该位置不能更改其内容。在 Java 中的 a 的情况下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
可变性和不变性的概念只与代码可能引用的事物相关。如果一个人持有对某物的引用,并且观察到该事物状态的某些不可变方面具有某个值(或状态),那么只要该引用存在,则可以始终观察到该事物状态的该方面具有相同的值(状态)。
Java 中的String
类型可以合理地描述为不可变的,因为引用字符串并观察到它包含字符“Hello”的代码可以随时检查它并始终观察到它包含这些字符。相比之下,aChar[]
可能在某一时刻被观察到包含字母“Hello”,但在稍后的某个时间被观察到包含字母“Jello”。因此,aChar[]
被认为是可变的。
因为在 Java 中不可能直接引用 an int
,所以可变性和不变性的概念并不真正适用于该类型。但是,可以持有对Integer
它们相关的 的引用。观察到具有特定值的任何此类引用将始终具有相同的值。因此,Integer
是不可变的。int
请注意,虽然可变性和不变性的概念并不真正适用于像或不可变类型保证不会更改,除非用新值或对不同不可变对象的引用覆盖该位置。
java中有一些类是不可变的,例如String,All Wrapper Class,即。整数、浮点数、长整数等。
例如:整数 i=5;我=10;我=15;
当 Integer i=5 时,这里会创建一个新的 Integer 对象,然后在第 2 次,i=10 而不是将这个值 10 分配给先前创建的对象,创建另一个新对象并分配给 i,第 3 次 i=15 ,在这里再次创建新对象并再次分配给 i。
注意:不要将 int 与 Integer 混淆。int 是原始类型,Integer 是包装类。所有原语都是可变的。
如果一个对象在构造后它的状态不能改变,那么它就被认为是不可变的。
来源:http ://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html
通常,这意味着您不能调用将更改类型(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
不可变对象是一旦实例化就不能修改的东西。如果必须修改,将创建一个新对象并指向该引用。
并且整数不是一成不变的。