Java基本类型是不可变的吗?

59
如果一个方法有一个局部变量 i
int i = 10;

然后我分配一个新值:

i = 11;

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

这是否意味着原始数据类型是不可变的?


1
Java原始类型不是对象。Integer(以及其他原始包装类)不可变的。 - Luiggi Mendoza
3
不,他没有。按照你的逻辑,字符串是可变的:String str = "test"; str = "newStr";。回答OP的问题,实际上它们是不可变的。如果考虑 i++,那么实际上是:i = i + 1。你可以看到它获取 i 的值,加一并将 i 重新赋值为这个新值。 - user1181445
1
抱歉,但你们现在让我感到困惑了..... - fYre
13
我认为这并不是一个糟糕的问题...不确定为什么有那么多的踩。 - arshajii
2
@BrianRoach 我也一直在研究 Ballmer 曲线 - user1181445
显示剩余5条评论
6个回答

85
这会分配新的内存位置吗?还是只是替换原来的值?
Java实际上并不保证变量与内存位置相对应;例如,您的方法可能被优化,以便将i存储在寄存器中,或者根本不存储它,如果编译器可以看到您从未实际使用其值,或者如果它可以跟踪代码并直接使用适当的值。
但是,抛开这个问题......如果我们假设局部变量表示调用堆栈上的内存位置,那么i = 11将仅修改该内存位置上的值。它不需要使用新的内存位置,因为变量i是唯一引用旧位置的东西。
这是否意味着基本数据类型是不可变的?
是和否:是的,基本数据类型是不可变的,但不是因为上述原因。
当我们说某物是可变的时,我们的意思是它可以被改变而仍具有相同的身份。例如,当你让头发长出来时,你正在改变自己:你仍然是你,但是你的一个属性已经改变了。
在基本数据类型的情况下,它们的所有属性都由它们的身份完全确定;1始终表示1,无论如何,1 + 1始终是2。你不能改变这个。
如果给定的int变量具有值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”来指代变量(其中包含一个引用),以及它所指代的对象(当它指代一个对象时)。只要你在重要时刻记得这个区别,这种松散的用法是可以接受的。


12

Immutable 意味着每次对象的值发生更改时,都会在堆栈中为其创建一个新的引用。只有包装类才是不可变的,原始类型无法谈及不可变性。

在Java中,使用的是按值传递的方式(copy_by_value)而非按引用传递。

无论传递的是基本类型还是引用变量,都传递了变量位中位表示值的拷贝。因此,对于基本类型变量,传递的是代表值的位的拷贝,如果传递的是对象引用变量,则传递的是代表对象引用的位的拷贝。

例如,如果传递一个值为3的int变量,则传递代表3的位的拷贝。

一旦声明了一个基本类型,它的基本类型永远不能改变,尽管它的值可以改变。


@Maroun Maroun 我刚试了以下代码: int i = 10; int j = i; syso(i); // 10 syso(j); // 10 i = 11;
syso(i) // 11 syso(j) // 10所以如果 i 没有在内存中创建新的位置,那么 j 的值怎么可能相同呢?
- fYre
1
在原始类型的情况下,当您将i的值分配给j时,与i的值对应的位被复制,您并没有分配i的引用,因为在原始类型的情况下无法谈论引用。 - Java Panter

12

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

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

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

enter image description here

对于对象来说,值(或引用)本身就是地址(或堆地址),因此如果更改它,其他人也会反映出来。例如,在对象中:

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

enter image description here

所以,无论是p1还是p2进行更改,都将对两者进行更改。无论是Java、Python还是Javascript,都是一样的。对于原始类型,它是实际值,但对于对象而言,则是实际对象的地址——这就是诀窍。


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的值时,实际上只是更改存储在该槽中的值。这些值本身并没有改变,只是它们的位置发生了变化。


0

是的,它们是不可变的。它们完全无法更改。

这里有一个很好的解释。虽然是针对Go语言的,但在Java或任何其他C系列语言中都是一样的。


1
不,它不是。原始类型是不可变的,而变量不是。 - nes1983

-1

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

任何原始变量的标识都是该变量的名称,显然这种标识是不可改变的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接