我了解到,当您在Java中修改变量时,它不会更改基于
的变量int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 as expected
System.out.println(b); // 10 as expected
我假设对象有类似的东西。考虑这个课程。
public class SomeObject {
public String text;
public SomeObject(String text) {
this.setText(text);
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
在我尝试这段代码后,我感到困惑。
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second as UNexpected
System.out.println(s2.getText()); // second as expected
请向我解释为什么更改任何对象会影响另一个对象。我知道变量文本的值存储在两个对象的内存中的相同位置。
为什么变量的值是独立的但与对象相关?
另外,如果简单赋值不能完成工作,如何复制SomeObject?
答案 0 :(得分:128)
Java中的每个变量都是引用。所以当你这样做时
SomeClass s2 = s1;
您只需将s2
指向与s1
指向的同一对象即可。实际上,您正在将引用s1的值(指向SomeClass
的实例)分配给s2。 如果修改s1
,s2
也会被修改(因为它指向同一个对象)。
有一个例外,原始类型:int, double, float, boolean, char, byte, short, long
。它们按值存储。因此,在使用=
时,您只需指定值,但它们不能指向同一个对象(因为它们不是引用)。这意味着
int b = a;
仅将b
的值设置为a
的值。 如果您更改a
,b
将不会更改。
在一天结束时,一切都是按值分配,它只是引用的值而不是对象的值(除了上面提到的基本类型)。
因此,在您的情况下,如果您想复制s1
,可以这样做:
SomeClass s1 = new SomeClass("first");
SomeClass s2 = new SomeClass(s1.getText());
或者,您可以向SomeClass
添加一个复制构造函数,该构造函数将实例作为参数并将其复制到自己的实例中。
class SomeClass {
private String text;
// all your fields and methods go here
public SomeClass(SomeClass copyInstance) {
this.text = new String(copyInstance.text);
}
}
通过这种方式,您可以非常轻松地复制对象:
SomeClass s2 = new SomeClass(s1);
答案 1 :(得分:38)
int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 as expected
System.out.println(b); // 10 as expected
int a = new Integer(5);
1-第一个语句创建一个值为5的Integer对象。然后,在将其分配给变量a
时,Integer对象将被取消装箱并作为基元存储在a
中。
创建Integer对象后,在赋值之前:
转让后:
int b = a;
2-这只会读取a
的值,然后将其存储到b
。
(整数对象现在有资格进行垃圾回收,但此时不一定是垃圾回收)
b = b + b;
3-这会将b
的值读取两次,将它们加在一起,然后将新值放入b
。
另一方面:
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second as UNexpected
System.out.println(s2.getText()); // second as expected
SomeObject s1 = new SomeObject("first");
1-创建SomeObject
类的新实例,并将其分配给引用s1
。
SomeObject s2 = s1;
2-这将使引用s2
指向s1
指向的对象。
s2.setText("second");
3-在引用上使用setter时,它将修改引用指向的对象。
System.out.println(s1.getText());
System.out.println(s2.getText());
4-两者都应打印second
,因为两个引用s1
和s2
指的是同一个对象(如上图所示)。
答案 2 :(得分:15)
当你这样做时
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
您有2个对同一对象的引用。这意味着无论您使用哪个参考对象,在使用第二个参考时,您所做的更改都将可见。
这样想:你在房间里有一台电视机,但有两个遥控器:你使用哪个遥控器无关紧要,你仍然会对相同的底层物体(电视)进行更改。
答案 3 :(得分:10)
当你写:
SomeObject s1 = new SomeObject("first");
s1
不是SomeObject
。它是SomeObject
对象的引用。
因此,如果将s2分配给s1,则只需指定引用/句柄,底层对象也是相同的。这在Java中很重要。一切都是按值传递,但你永远不会传递对象 - 只引用对象。
因此,当您指定s1 = s2
,然后调用s2
上更改对象的方法时,基础对象会发生变化,并且当您通过s1
引用对象时,该对象可见。
这是对象中 immutability 的一个参数。通过使对象不可变,它们不会在您之下发生变化,从而以更可预测的方式运行。如果要复制对象,最简单/最实用的方法是编写一个copy()
方法,只需创建一个新版本并复制字段即可。你可以使用序列化/反射等来做聪明的复制,但这显然更复杂。
答案 4 :(得分:4)
来自Top Ten Errors Java Programmers Make :
6 - 对按值传递和通过引用传递的混淆
这可能是一个令人沮丧的诊断问题,因为当你看 在代码中,您可能确定它通过引用传递,但查找 它实际上是通过价值传递的。 Java使用两者,因此您需要 了解你何时通过价值,以及何时路过 参考
传递基本数据类型时,例如char,int,float或 加倍,到函数然后你传递值。这意味着一个 复制数据类型的副本,并传递给该函数。如果 函数选择修改该值,它将修改 只复制。一旦函数完成,并将控制权返回给 返回函数,“真实”变量将不受影响,并且不会 更改将被保存。如果需要修改原始数据 type,使其成为函数的返回值,或将其包装在一个函数中 对象
由于int
是基本类型,int b = a;
是按值复制,这意味着a
和b
是两个不同的对象,但具有相同的值。
SomeObject s2 = s1;
使s1
和s2
对同一个对象进行两次引用,因此如果您修改了一个,则另一个也会被修改。
一个好的解决方案是实现这样的另一个构造函数:
public class SomeObject{
public SomeObject(SomeObject someObject) {
setText(someObject.getText());
}
// your code
}
然后,使用它:
SomeObject s2 = new SomeObject(s1);
答案 5 :(得分:2)
在您的代码中s1
和s2
是相同的对象(您只使用new
创建了一个对象),然后让s2
指向下一行中的同一个对象。因此,当您更改text
时,如果您通过s1
和s2
引用该值,则会更改两者。
整数上的+
运算符创建一个 new 对象,它不会更改现有对象(因此添加5 + 5不会给新值10 ...)
答案 6 :(得分:2)
那是因为JVM存储了一个指向s1
的指针。当你调用s2 = s1
时,你基本上是说s2
指针(即内存地址)的值与s1
的值相同。由于它们都指向内存中的相同位置,因此它们代表完全相同的东西。
=
运算符指定指针值。它不会复制对象。
克隆对象本身就是一个复杂的问题。每个对象都有一个clone()方法,您可能会尝试使用它,但它会执行浅层复制(这基本上意味着它会创建顶级对象的副本,但不会克隆其中包含的任何对象)。如果你想玩对象副本,请务必阅读Joshua Bloch的Effective Java。
答案 7 :(得分:2)
让我们从你的第二个例子开始:
此第一个语句将新对象分配给s1
SomeObject s1 = new SomeObject("first");
当您在第二个语句(SomeObject s2 = s1
)中执行赋值时,您告诉s2
指向当前指向的同一个对象s1
,因此您有两个引用同一个对象。
请注意,您没有复制SomeObject
,而是两个变量只指向同一个对象。因此,如果您要修改s1
或s2
,则实际上是在修改同一个对象(请注意,如果您执行类似s2 = new SomeObject("second")
的操作,他们现在会指向不同的对象)。
在您的第一个示例中,a
和b
是原始值,因此修改一个不会影响另一个。
在Java的引擎下,所有对象都使用按值传递。对于对象,您传递的是您传递的“值”是对象在内存中的位置(因此它似乎具有类似的按引用传递的效果)。基元表现不同,只传递值的副本。
答案 8 :(得分:2)
上面的答案解释了你所看到的行为。
回答“另外,如果复制SomeObject,如果简单的赋值不能完成这项工作?” - 尝试搜索cloneable
(这是一个提供一种复制对象的方法的java接口)和'copy constructors
'(替代方案,可以说是更好的方法)
答案 9 :(得分:2)
对引用的对象分配不会克隆您的对象。引用就像指针。它们指向一个对象,并且在调用操作时,它将在指针指向的对象上完成。在您的示例中,s1和s2指向同一个对象,setter更改同一对象的状态,并且更改在引用中可见。
答案 10 :(得分:2)
更改您的课程以创建新的参考而不是使用相同的参考:
public class SomeObject{
public String text;
public SomeObject(String text){
this.setText(text);
}
public String getText(){
return text;
}
public void setText(String text){
this.text = new String(text);
}
}
你可以使用这样的东西(我不假装有理想的解决方案):
public class SomeObject{
private String text;
public SomeObject(String text){
this.text = text;
}
public SomeObject(SomeObject object) {
this.text = new String(object.getText());
}
public String getText(){
return text;
}
public void setText(String text){
this.text = text;
}
}
用法:
SomeObject s1 = new SomeObject("first");
SomeObject s2 = new SomeObject(s1);
s2.setText("second");
System.out.println(s1.getText()); // first
System.out.println(s2.getText()); // second
答案 11 :(得分:2)
int a = new Integer(5)
在上面的例子中,创建了一个新的Integer。这里Integer是一种非原始类型 其中的值被转换(转换为int)并分配给int'a'。
SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
在这种情况下,s1和s2都是引用类型。它们不是为了包含类似基本类型的值而创建的,而是包含某个对象的引用。为了便于理解,我们可以将引用视为一个链接,指示我可以在哪里找到被引用的对象。
这里引用s1告诉我们在哪里可以找到“first”的值(实际上它存储在SomeObject实例中的计算机内存中)。换句话说,s1是类“SomeObject”的对象的地址。通过以下任务 -
SomeObject s2 = s1;
我们只是将存储在s1中的值复制到s2,我们现在知道s1包含字符串“first”的地址。 在这个伪装之后,println()都会产生相同的输出,因为s1和s2都会刷新同一个对象。
如果你是一个java,你可以使用clone()方法复制对象和复制构造函数 用户。克隆可以按以下方式使用 -
SomeObject s3 = s1.clone();
有关clone()的更多信息,这是一个有用的链接http://en.wikipedia.org/wiki/Clone_%28Java_method%29
答案 12 :(得分:1)
第二行(SomeObject s2 = s1;
)只是将第二个变量赋给第一个变量。这导致第二个变量指向与第一个相同的对象实例。
答案 13 :(得分:1)
这是因为s1
和s2
仅用作对象的引用。分配s2 = s1
时,您只需指定引用,这意味着两者都将指向内存中的同一对象(当前文本为“first”的对象)。
当你现在做一个setter时,无论是在s1还是s2,两者都会修改同一个对象。
答案 14 :(得分:1)
当您为变量赋值和对象时,您确实正在为该对象分配引用。因此,变量s1
和s2
都指的是同一个对象。
答案 15 :(得分:1)
由于引用引用了对象。因此,如果您编写s2 = s1,则仅复制引用。就像在C中一样,你只处理内存地址。如果你复制一个内存的地址并改变这个地址后面的值,两个指针(引用)将改变内存中此时的一个值。