在Java中设置相等:按值还是按引用?

10

我做了两个测试,第一个是从 Strings 开始的。

    String str1 = "old";
    String str2 = str1;
    str1 = "new";

    System.out.println(str1); //new
    System.out.println(str2); //old

以上示例表明 str2 = str1,按值传递

现在我进行类似的操作,但这次使用列表(Lists)

    List<Integer> list1 = new ArrayList<Integer>();
    List<Integer> list2 = list1;
    list1.add(1);

    System.out.println(list1.size()); //1
    System.out.println(list2.size()); //1

这个例子表明list2 = list1,通过引用传递

我感到困惑,Java的哪些变量/对象是按值传递,哪些是按引用传递


4
所有的参数都是按值传递,没有按引用传递的。 - Sotirios Delimanolis
1
@onepiece 因为你正在复制引用。 - Deepak Ingole
请记住,字符串是不可变的! :D - user_loser
1
区别在于 str1 = "new" 将一个新对象赋给了变量;但 list1.add(1) 修改了一个已存在的对象,而不创建新对象。 - Dawood ibn Kareem
1
onepiece - 所有Java变量(除了原始类型)都是引用变量。这意味着每当您使用一个变量时,您都在对引用进行操作。这不像C语言,其中所有变量都是值变量。这意味着在Java中,“按引用传递”和“按值传递”的区别不存在。我们按值传递,并且传递的是引用。 - Dawood ibn Kareem
显示剩余2条评论
4个回答

7
在你的第一段代码中,是的,这一行
String str2 = str1;

str2分配给与str1引用的相同的String,即"old"。此时,它们是同一个对象。但是,接下来的一行

str1 = "new";

创建一个String类的实例,并将str1的引用更改为这个新的字符串。由于我们正在更改str1的引用,所以str2的内容并不会改变。
需要注意的是,在Java中,String是不可变的,即一旦初始化就不能更改状态。按照这种思路,"old"的内容可能永远不会改变。因此,当您将"new"分配给str1时,您并没有改变"old"的值,而是创建了一个新的String
换句话说,这行代码与此处相同:
str1 = new String("new");

http://i.minus.com/jboQoqCxApSELU.png

然而,在第二段代码中,

List<Integer> list2 = list1;

使list2引用与list1相同的列表。结果,list1list2引用相同的列表。

list1.add(1); 

向由list1引用的列表添加一个元素。但是,正如我所说的,list1list2引用同一个列表,因此list1list2现在都有元素1。在方法调用中没有创建新实例。

http://i.minus.com/jxDLyBqcUzgHZ.png

事实上,如果你这样做

List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = list1;
list1 = new ArrayList<Integer>();
list1.add(1);

System.out.println(list1.size()); //1
System.out.println(list2.size()); //0

因为list1 = new ArrayList<Integer>();会将list1重新分配给一个新的列表,那个新列表不再指向list2所指向的对象。
在所有情况下,赋值运算符(即obj1 = obj2)始终复制引用,这两个引用在赋值后仍然引用同一对象实例。这适用于StringList任何其他类(但不适用于基本类型)。
然而,在大多数情况下,str1 = "new"会创建一个String的新实例,然后将引用分配给新的String - 这是Java语言的特例。这不适用于其他任何类型的对象。这与任何其他方法调用(如list1.add(1))不同。

请纠正我如果我错了:当将某个不可变对象设置为另一个对象时,它会创建自己的新实例,而可变类则会引用相同的内容,除非手动实例化它们(使用关键字“new”)。 - onepiece
实际上,所有的赋值都会引用同一个对象。然而,在Java中,str1 = "new";是一个特殊情况,它会创建一个新的String对象 - 这是合理的考虑到String是不可变的。 - luiges90
1
不!你可以使用List来创建第一个示例。这与字符串和列表之间的任何固有差异无关,而是与赋值运算符(=)和调用方法(如add)之间的区别有关。 - Dawood ibn Kareem
2
@onepiece 只有两种类型,原始类型和引用类型。不要认为是 String vs List。它们都是引用类型,工作方式相同。 - Sotirios Delimanolis
1
@luiges90,通过调用变量的方法无法改变变量引用的值。David是完全正确的。 - Sotirios Delimanolis
显示剩余4条评论

4

你的不同之处就在这里

str1 = "new";

vs

list1.add(1);

String 的例子中,你正在更改引用。更改 str1 的引用不会影响任何其他变量。
List 的例子中,你正在调用一个方法,该方法取消引用引用并访问对象。引用同一对象的任何变量都将看到该更改。
就是这样。
List<Integer> list1 = new ArrayList<Integer>(); // 1
List<Integer> list2 = list1; // 2 
list1.add(1); // 3

看起来像这样

   1:  list1 ===> object1234
   2:  list1 ===> object1234 <=== list2
   3:  list1 ===> object1234 (internal modification) <=== list2

任何引用同一对象的变量都将看到该更改。但是,如果Java按值传递,则list2应具有list1的值,而不是引用。 - onepiece
@onepiece 值是引用。 - Sotirios Delimanolis
@onepiece 请逐步查看。 - Sotirios Delimanolis
这个答案是最清晰和最直接的。+1,因为它没有提到“不可变性”,也没有暗示字符串在某种程度上是特殊的。 - Dawood ibn Kareem

0

Java中的一切都是按值传递的,没有类似于引用传递的方式。对象引用是按值传递的。

String str1="old"
String str2=str1; //here str2 is pointed to str1
str1="new";       // here link of str1 is broken with old and pinted to new location      where as str2 is pointing to same location as earlier.

在列表的情况下,两个列表都指向同一内存位置,因此更改会反映出来。


在Java中,数组是隐式按引用传递的。这意味着在函数中对数组所做的更改可以反映原始内容。 - Balayesu Chilakalapudi

0

Java中全部都是按值传递。Java没有按引用传递。变量的副本被传递到可能是方法参数或常规普通变量的变量中。例如字符串文字、字符串、数组引用和ArrayLists等对象仍然按值传递。

问题在于,如果引用未被重新分配,则引用变量发生的任何事情都将发生在它所引用的对象上。

请查看这个教程,来自编写《Head First Java》和其他书籍的人:http://www.javaranch.com/campfire/StoryPassBy.jsp

此外,在您的代码中,请记住字符串是不可变的。这意味着一旦创建了对象,它基本上不会改变。但是,在您的代码中,字符串引用被重新分配,而List引用只是调用了一个方法。List引用都指向完全相同的ArrayList对象;因此,对它们中的任何一个进行的方法调用size()都会影响两个List引用,因为它们都指向堆内存中的同一个对象。 :D

愉快的编码!


2
最后一段是一个误导。即使“String”对象不是不可变的,这个特定的例子仍然会表现出相同的行为。 - Dawood ibn Kareem
嗨,David。我读你的评论时用了新西兰口音,因为你来自那里。:D 无论如何,你可能是对的,如果没有在我的机器上编码并进行检查的话。这是因为字符串引用中有重新分配,而列表引用中没有。:D 谢谢你的反馈。我太无聊了。:D - user_loser
@DavidWallace 我更新了我的答案。感谢你的认真建议。 :D - user_loser
1
我认为你的编辑并没有改进。很抱歉,但是我对最后一句话完全感到困惑。我只看到一个ArrayList对象,这也是问题的重点。 - Dawood ibn Kareem
哦,没错,再来一次。如果有两个java.util.ArrayList()对象,那甚至都没有意义。:D 再次感谢您,Wallace先生。:D - user_loser

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