在Java中复制对象
我了解到,当您在Java中修改variables时,它不会更改它所基于的variables
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
请向我解释为什么改变任何物体会影响另一个物体。 我知道variables文本的值被存储在两个对象的内存中的相同位置。
为什么variables的值是独立的,但与对象相关?
此外,如何复制SomeObject,如果简单的分配不做这项工作?
Java中的每个variables都是一个参考 。 所以,当你这样做
SomeClass s2 = s1;
你只需将s2
指向与s1
指向的相同对象。 实际上,将引用s1(指向SomeClass
一个实例)的值分配给s2。 如果修改s1
, s2
也会被修改 (因为它指向同一个对象)。
有一个例外,原始types: int, double, float, boolean, char, byte, short, long
。 它们是按值存储的。 所以当使用=
,你只分配值,但是它们不能指向相同的对象(因为它们不是引用)。 这意味着
int b = a;
只将b
的值设置为b
的值。 如果你改变a
, b
不会改变。
在一天结束时,所有东西都是按值赋值的,它只是引用的值,而不是对象的值(除了上面提到的基本types之外)。
所以在你的情况下,如果你想做一个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);
@ brimborium的回答非常好(他为+1),但我只是想用一些数字来详细说明。 先来看看原始的作业:
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-第一条语句创build一个值为5的Integer对象。然后,在将其赋值给variablesa
,Integer对象将被拆箱并作为基元存储在a
中。
在创buildInteger对象之后,在赋值之前:
分配后:
int b = a;
2-这将只读取a的值,然后将其存储到b
。
(Integer对象现在有资格进行垃圾回收,但是目前还不一定是垃圾回收)
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-创buildSomeObject
类的新实例,并将其分配给引用s1
。
SomeObject s2 = s1;
这将使得参考点s2
指向s1
指向的对象。
s2.setText("second");
3-在引用上使用setter时,它将修改引用指向的对象。
System.out.println(s1.getText()); System.out.println(s2.getText());
4-两者都应该打印second
,因为两个引用s1
和s2
是指同一个对象(如上图所示)。
当你这样做
SomeObject s1 = new SomeObject("first"); SomeObject s2 = s1;
你有2个对同一个对象的引用。 这意味着无论您使用哪个参考对象,使用第二个参考时所做的更改都将可见。
你可以这样想:房间里有一台电视机,但是有两台遥控器:使用哪一台遥控器并不重要,你仍然在对同一个基础对象(电视机)进行改变。
当你写:
SomeObject s1 = new SomeObject("first");
s1
不是SomeObject
。 它是对SomeObject
对象的引用 。
因此,如果将s2分配给s1,则只需简单地分配引用/句柄,而底层对象也是相同的。 这在Java中很重要。 所有东西都是按值传递的,但是你永远不会传递对象 – 只能引用对象。
因此,当您分配s1 = s2
,然后调用s2
上更改对象的方法时,基础对象将发生更改,并且在通过s1
引用对象时可见。
这是对物体不变性的一个论据。 通过使对象不可改变,它们不会在你的下面改变,从而以更可预测的方式performance。 如果你想复制一个对象,最简单/最实用的方法是编写一个copy()
方法,只需创build一个新的版本并复制这些字段。 你可以使用序列化/reflection等巧妙的副本,但是这显然更复杂。
从十大错误Java程序员使 :
6 – 对价值传递的困惑,通过参照传递
这可能是一个令人沮丧的问题来诊断,因为当你看代码的时候,你可能确定它是通过引用传递的,但是发现它实际上是按值传递的。 Java使用两者,因此您需要了解何时按值传递,何时通过引用传递。
将基本数据types(如char,int,float或double)传递给某个函数时,您将按值传递。 这意味着数据types的副本被复制,并传递给函数。 如果函数select修改该值,它将只修改副本。 一旦函数完成,并且控制返回到返回函数,“真实”variables将保持不变,并且不会保存任何更改。 如果您需要修改基本数据types,请将其设置为函数的返回值,或者将其包含在对象中。
因为int
是一个原始types, int b = a;
是按值复制的,这意味着a
和b
是两个不同的对象,但具有相同的值。
SomeObject s2 = s1;
让s1
和s2
两个引用同一个对象,所以如果你修改一个,另一个也会被修改。
一个好的解决scheme是实现另一个像这样的构造函数:
public class SomeObject{ public SomeObject(SomeObject someObject) { setText(someObject.getText()); } // your code }
然后,像这样使用它:
SomeObject s2 = new SomeObject(s1);
在你的代码中, s1
和s2
是同一个对象(你只用new
创build了一个对象),你让s2
指向下一行中的同一个对象。 因此,如果您通过s1
和s2
引用该值,那么当您更改text
它将同时更改。
整数上的+
运算符创build一个新的对象,它不会改变现有的对象(所以添加5 + 5不会给新的值10 …)。
这是因为JVM存储了一个指向s1
的指针。 当你调用s2 = s1
,你基本上是说s2
指针(即内存地址)与s1
值相同。 既然他们都指向记忆中的同一个地方,他们代表完全一样的东西。
=
运算符分配指针值。 它不会复制对象。
克隆对象本身是一件复杂的事情。 每个对象都有一个你可能试图使用的clone()方法,但是它做了一个浅拷贝(这基本上意味着它复制了顶层对象,但是其中包含的任何对象都没有被克隆)。 如果你想玩对象副本,一定要阅读Joshua Bloch的Effective Java 。
我们从第二个例子开始:
这第一条语句为s1
分配一个新的对象
SomeObject s1 = new SomeObject("first");
当你在第二个语句( SomeObject s2 = s1
)中进行赋值时,你告诉s2
指向s1
当前指向的同一个对象,所以你有两个对同一个对象的引用。
请注意,您没有复制SomeObject
,而是两个variables只是指向同一个对象。 所以,如果你修改s1
或s2
,你实际上是修改同一个对象(注意如果你做了一些像s2 = new SomeObject("second")
他们现在将指向不同的对象)。
在你的第一个例子中, a
和b
是原始值,所以修改一个不会影响另一个。
在Java的引擎下,所有对象都使用按值传递。 对于对象来说,你传递的是正在传递的“值”,是对象在内存中的位置(所以它看起来有类似于传递引用的效果)。 基元的行为不同,只是传递一个值的副本。
上面的答案解释你所看到的行为。
在回答“另外,如何复制SomeObject,如果简单的赋值不做这个工作? – 尝试search可cloneable
(这是一个提供一种方法来复制对象的Java接口)和“ copy constructors
”(一种替代方法,可以说是更好的方法)
将对象分配给引用不会克隆您的对象。 引用就像指针。 它们指向一个对象,当调用操作时,它将在指针指向的对象上完成。 在你的例子中,s1和s2指向同一个对象,setter改变同一个对象的状态,这些改变在引用中是可见的。
改变你的类,以创build新的参考,而不是使用同一个:
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); } }
你可以使用这样的东西(我不假装有理想的解决scheme):
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
int a = new Integer(5)
在上面的情况下,创build一个新的Integer。 这里Integer是一个非原始types,其中的值被转换(为一个int)并赋值给一个int'a'。
SomeObject s1 = new SomeObject("first"); SomeObject s2 = s1;
在这种情况下,s1和s2都是引用types。 它们不是被创build为包含像基本types的值,而是包含某个对象的引用。 为了理解,我们可以把引用看作一个链接,表示我可以在哪里find被引用的对象。
这里引用s1告诉我们在哪里可以find“first”的值(实际上存储在SomeObject的一个实例中的计算机的内存中)。换句话说,s1是类“SomeObject”的对象的地址。 通过以下任务 –
SomeObject s2 = s1;
我们只是将存储在s1中的值复制到s2中,现在我们知道s1包含string“first”的地址。 在这个assigenment之后,println()产生相同的输出,因为s1和s2都折回同一个对象。
除了复制构造函数外,如果您是java用户,则可以使用clone()方法复制对象。 克隆可以以下列方式使用 –
SomeObject s3 = s1.clone();
有关克隆()的更多信息,这是一个有用的链接http://en.wikipedia.org/wiki/Clone_%28Java_method%29
那是因为s1
和s2
只能作为你的对象的引用。 当分配s2 = s1
,只分配引用,意味着两者都将指向内存中的同一个对象(当前文本为“first”的对象)。
当你现在做一个setter时,无论是在s1还是s2上,两者都会修改同一个对象。
当你分配和对一个variables,你真的是分配对该对象的引用。 所以variabless1
和s2
都指向同一个对象。
由于对象被引用引用。 所以,如果你写s2 = s1只有参考将被复制。 它像在C中,你只处理内存地址。 如果你复制一个内存的地址并改变这个地址后面的值,那么这两个指针(引用)都会在内存中改变这个值。
第二行( SomeObject s2 = s1;
)简单地将第二个variables赋值给第一个variables。 这导致第二个variables指向与第一个相同的对象实例。