当我们说"ints are immutable"时,我们到底是什么意思
what do we mean exactly when we say "ints are immutable"
我对"不可变"的概念感到困惑。我们的教授说,整型是不可变的!字符串是不可变的,这是什么意思呢?
一个更普遍的问题,我们如何知道数据结构是否是不可变的?
谢谢
这里的其他一些答案将可变性/不可变性与值/引用语义混淆,所以要小心…
简单地说,如果一个实体在创建之后可以被修改,那么它就是可变的。换句话说,它的值可能会随时间变化。
首先是一个反例。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("%dn", *p); // 4
当然,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"
字符串是容器,因为它们包含一定数量的单个字符:这里的a
、b
和c
。如果我想将第二个字符更改为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
所代表的数字。所以我们称它们为不可变的
在Smalltalk中很容易改变int
:
3 become: 4
这将将中的3更改为4,覆盖之前包含3的内存。如果int
被嵌入(就像在Python中一样),这甚至可能意味着3
在源代码中出现的任何地方,它的行为就像数字4。
在C中,这些区别没有意义,因为变量是固定的内存块,而不是Python的瞬态标签。当你这样做的时候:
int x = 3;
x = 4;
很难说这是否在"变异"整型。它会覆盖现有内存,但这也是C变量赋值的工作方式。
!可变性只是关于你是在改变一个现有的对象还是用一个新的对象来替换它。在Python和Java中,你不能改变现有的字符串,也不能"改变"数字,所以我们称它们为不可变的。您可以自由地就地更改列表和数组的内容,而无需创建新的列表和数组,因此它们是可变的。
什么是不可变的是高度依赖于语言的,但是不可变对象就是在创建之后不能更改的对象。
这通常意味着:
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!"而是"Hello":
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
如果对象在构造后状态不能改变,则认为该对象是不可变的。
source: 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
不可变对象是一旦实例化就不能修改的对象。如果必须修改,则将创建一个新对象并将其指向引用。
int型不是不可变的。
java中有一些类是不可变的,如String, All Wrapper class。整型、浮点型、长型等
例如:整数我= 5;我= 10;我= 15;
当Integer i=5时,这里创建了一个新的Integer对象,然后在第2次,i=10而不是将这个值10赋给先前创建的对象,创建了另一个新对象并赋值给i,第三次i=15,这里再次创建新对象并再次赋值给i。
注意:不要将int与Integer混淆。int是基本类型,Integer是包装类。所有的原语都是可变的。
可变性和不可变性的概念仅与代码可能持有引用的事物相关。如果一个人持有某物的引用,并且观察到该物状态的某些不可变方面具有某些值(或状态),那么只要该引用存在,该物状态的该方面可能总是具有相同的值(状态)。
Java中的String
类型可以合理地描述为不可变的,因为代码有一个对字符串的引用,并且观察到它包含字符"Hello",可以在任何时候检查它,并且总是观察到它包含这些字符。相比之下,Char[]
可能在某一时刻被观察到包含字母"Hello",但在稍后的时间被观察到包含字母"Jello"。因此,Char[]
被认为是可变的。
因为在Java中不可能持有对int
的直接引用,所以可变性和不可变性的概念并不真正适用于该类型。但是,可以保留对Integer
的引用,因为它们是相关的。任何这样的被观察到具有特定值的引用将始终具有相同的值。因此,Integer
是不可变的。请注意,虽然可变性和不可变性的概念并不真正适用于像int
这样的值类型,但它们确实共享了不可变类型的一个有用方面:由原始类型或不可变类型的存储位置(变量、字段或数组元素)表示的状态保证不会改变,除非用新值或对不同不可变对象的引用覆盖该位置。
- int数据类型的指针指向的是什么,如果是一个类的私有数据成员,我们创建了该类的两个对象?
- 当我们已经有 char[] 时,在 c++ 中字符串的必要性是什么?
- 在这种情况下,我们可以使用静态而不是朋友吗,还有其他解决方案是什么
- 当我们在MSDN中调用UpdateWindows()时,该方法是什么
- 当我们关闭QT中的窗口时,发出的信号是什么
- 我们无法在本地类中定义朋友函数的原因是什么?
- 当我们说在执行程序时将操作系统的控制权传递给main()函数时,我们的意思是什么
- 如果我们可以使用 vector[1] = someInt,emplace() 的目的是什么
- 以下错误的原因是什么?我们在编译 Thrift C++ 代码时收到此错误
- 在类映射中,当我们有迭代器时,constiterator的必要性是什么
- 当我们在 c++ 中将常量整数引用分配给整数指针时,行为是什么
- 我们可以动态链接 DLL 的不同方法是什么
- 视觉 C++当我们在基类中使函数成为纯虚拟时,那么在子类中再次使相同的函数虚拟的必要性是什么
- 对于SparseMatrix实现,我们能做的最好的事情是什么?
- 当我们对没有任何数据成员的类的对象使用sizeof运算符时.那么输出会是什么
- (C++内存管理)如果我们有一个 int x = 1234,并且 int &y = x...那么堆栈上 y 的地址是什么?
- 第二个参数和第三个参数在fwrite()中的作用是什么?为什么我们在这里需要 3rcount?
- 当我们从用户模式转移到内核模式时,哪些寄存器会发生变化?!以及转移到内核模式的原因是什么
- 当我们可以使用普通函数修改静态数据成员时,静态成员函数的需求是什么?
- 当我们说"ints are immutable"时,我们到底是什么意思