53

如果一个方法有一个局部变量i

int i = 10;

然后我分配一个新值:

i = 11;

这会分配一个新的内存位置吗?还是只是替换原始值?

这是否意味着原语是不可变的?

4

6 回答 6

80

这会分配一个新的内存位置吗?还是只是替换原始值?

Java 并没有真正保证变量将对应于内存位置。例如,您的方法可能会以i存储在寄存器中的方式进行优化——或者甚至可能根本不存储,如果编译器可以看到您从未真正使用过它的值,或者如果它可以跟踪代码并直接使用适当的值。

但把它放在一边。. . 如果我们在这里抽象为局部变量表示调用堆栈上的内存位置,那么i = 11将简单地修改该内存位置的值。它不需要使用新的内存位置,因为变量i是唯一引用旧位置的东西。

这是否意味着原语是不可变的?

是与否:是的,原语是不可变的,但不,这不是因为上述原因。

当我们说某些东西是可变的时,我们的意思是它可以被改变:改变但仍然具有相同的身份。例如,当你长出头发时,你正在改变自己:你仍然是你,但你的一个属性是不同的。

在原语的情况下,它们的所有属性完全由它们的身份决定;1always 意味着1,无论如何,并且1 + 1总是2。你无法改变这一点。

如果给定int变量具有 value 1,您可以将其更改为具有该值2,但这是身份的完全改变:它不再具有与以前相同的值。这就像改变me指向别人而不是指向我:它实际上并没有改变,它只是改变了me

当然,对于对象,您通常可以同时做到以下两点:

StringBuilder sb = new StringBuilder("foo");
sb.append("bar"); // mutate the object identified by sb
sb = new StringBuilder(); // change sb to identify a different object
sb = null; // change sb not to identify any object at all

通俗地说,这两者都将被描述为“变化sb”,因为人们将使用“ sb”来指代变量(包含引用)和它所指的对象(当它指代一个时)。这种松散是可以的,只要你记住重要的区别。

于 2013-08-03T21:35:35.460 回答
11

Immutable意味着每次和对象的值发生变化时,都会在堆栈上为它创建一个新的引用。对于原始类型,您不能谈论不可变性,只有包装类是不可变的。Javacopy_by_value不通过引用使用。

如果您传递原始变量或引用变量,这没有区别,您总是传递变量中位的副本。因此,对于原始变量,您传递的是表示值的位的副本,如果您传递的是对象引用变量,则传递的是表示对对象的引用的位的副本。

例如,如果你传递一个值为 3 的 int 变量,你传递的是代表 3 的位的副本。

一旦声明了一个原语its primitive type can never change,尽管它的值可以改变。

于 2013-08-03T20:54:59.367 回答
7

让我们更进一步,在其中添加另一个变量 j。

int i = 10;
int j = i;
i = 11

在 java 中,为 i 和 j 的值分配了 8 个字节的内存(i 为 4 个字节,j 为 4 个字节)。i 的值被传递给 j,现在 j 和 i 具有相同的值但不同的内存地址。现在 i 的值更改为 11,这意味着对于相同的内存地址 i 的值从 10 更改为 11,但 j 的值位于不同的内存位置,因此它保持为 10。

在此处输入图像描述

在对象的情况下,值(或引用)本身就是一个地址(或堆地址),因此如果有人更改它,它也会反映给其他人。例如在对象中:-

Person p1 = new Person();
Person p2 = p1;

在此处输入图像描述

因此,要么 p1 进行更改,要么 p2 进行更改,两者都会被更改。无论是 Java、Python 还是 Javascript,都是一样的。在原始的情况下,它是实际值,但在对象的情况下,它是实际对象的地址——这就是诀窍。

于 2019-12-22T17:13:49.000 回答
1

这不是一个完整的答案,但它是一种证明原始类型值不变性的方法。

如果原始值(文字)是可变的,则以下代码可以正常工作:

int i = 10; // assigned i the literal value of 10
5 = i; // reassign the value of 5 to equal 10
System.out.println(5); // prints 10

当然,这不是真的。

整数值,例如 5、10 和 11 已经存储在内存中。当您设置一个等于其中之一的变量时:它会更改内存插槽中的i值。

您可以通过以下代码的字节码在此处看到这一点:

public void test(){
    int i = 10;
    i = 11;
    i = 10;
}

字节码:

// access flags 0x1
public test()V
 L0
  LINENUMBER 26 L0
  BIPUSH 10 // retrieve literal value 10
  ISTORE 1  // store it in value at stack 1: i
 L1
  LINENUMBER 27 L1
  BIPUSH 11 // same, but for literal value 11
  ISTORE 1
 L2
  LINENUMBER 28 L2
  BIPUSH 10 // repeat of first set. Still references the same literal 10. 
  ISTORE 1 
 L3
  LINENUMBER 29 L3
  RETURN
 L4
  LOCALVARIABLE this LTest; L0 L4 0
  LOCALVARIABLE i I L1 L4 1
  MAXSTACK = 1
  MAXLOCALS = 2

正如您在字节码中看到的(希望如此),它引用了文字值(例如:10),然后将其存储在变量的插槽中i。当您更改 的值时i,您只是更改了存储在该插槽中的值。价值观本身并没有改变,它们的位置是。

于 2013-08-04T01:32:52.027 回答
0

是的,它们是不可变的。它们是完全不变的。

这里有一个很好的解释。它适用于 Go,但在 Java 中也是如此。或 C 系列中的任何其他语言。

于 2013-08-03T21:07:41.340 回答
-1

原始文字和final原始变量是不可变的。不是final原始变量是可变的。

任何原始变量的身份都是该变量的名称,很明显,这样的身份是不可更改的。

于 2018-09-09T21:00:57.140 回答