当我们说"ints are immutable"时,我们到底是什么意思

what do we mean exactly when we say "ints are immutable"

本文关键字:我们 是什么 意思 immutable ints are      更新时间:2023-10-16

我对"不可变"的概念感到困惑。我们的教授说,整型是不可变的!字符串是不可变的,这是什么意思呢?

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

谢谢

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


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

首先是一个反例。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"

字符串是容器,因为它们包含一定数量的单个字符:这里的abc。如果我想将第二个字符更改为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__(构造函数方法的名称),因此为所有这些相同的名称重用相同的字符串对象可以节省大量浪费的空间。

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

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

关于 ints。这不是一个不变性的例子:

x = 3
x = 4

这根本不涉及实际的对象;它只给变量x赋一个新值。

考虑:

x = [1, 2, 3]
y = x
x[:] = [4, 5, 6]
print y  # [4, 5, 6]

x[:] =语法是"切片表示法",用于替换列表的整个内容。这里,xy是同一个列表的两个名称。所以当你替换x的内容时,你也会在y中看到同样的效果,因为…他们的名字都是一样的。(这与其他语言中的引用变量不同:您可以将新值赋给xy,而不会影响另一个。)

以数字为例。如果您可以对普通数字执行类似上述的假设操作,则会发生以下情况:

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这样的值类型,但它们确实共享了不可变类型的一个有用方面:由原始类型或不可变类型的存储位置(变量、字段或数组元素)表示的状态保证不会改变,除非用新值或对不同不可变对象的引用覆盖该位置。

相关文章: