在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。 如果修改s1s2也会被修改 (因为它指向同一个对象)。

有一个例外,原始types: int, double, float, boolean, char, byte, short, long 。 它们是按值存储的。 所以当使用= ,你只分配值,但是它们不能指向相同的对象(因为它们不是引用)。 这意味着

 int b = a; 

只将b的值设置为b的值。 如果你改变ab不会改变。

在一天结束时,所有东西都是按值赋值的,它只是引用的值,而不是对象的值(除了上面提到的基本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 ,因为两个引用s1s2是指同一个对象(如上图所示)。

当你这样做

 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; 是按值复制的,这意味着ab是两个不同的对象,但具有相同的值。

SomeObject s2 = s1;s1s2两个引用同一个对象,所以如果你修改一个,另一个也会被修改。

一个好的解决scheme是实现另一个像这样的构造函数:

 public class SomeObject{ public SomeObject(SomeObject someObject) { setText(someObject.getText()); } // your code } 

然后,像这样使用它:

 SomeObject s2 = new SomeObject(s1); 

在你的代码中, s1s2同一个对象(你只用new创build了一个对象),你让s2指向下一行中的同一个对象。 因此,如果您通过s1s2引用该值,那么当您更改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只是指向同一个对象。 所以,如果你修改s1s2 ,你实际上是修改同一个对象(注意如果你做了一些像s2 = new SomeObject("second")他们现在将指向不同的对象)。

在你的第一个例子中, ab是原始值,所以修改一个不会影响另一个。

在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

那是因为s1s2只能作为你的对象的引用。 当分配s2 = s1 ,只分配引用,意味着两者都将指向内存中的同一个对象(当前文本为“first”的对象)。

当你现在做一个setter时,无论是在s1还是s2上,两者都会修改同一个对象。

当你分配和对一个variables,你真的是分配对该对象的引用。 所以variabless1s2都指向同一个对象。

由于对象被引用引用。 所以,如果你写s2 = s1只有参考将被复制。 它像在C中,你只处理内存地址。 如果你复制一个内存的地址并改变这个地址后面的值,那么这两个指针(引用)都会在内存中改变这个值。

第二行( SomeObject s2 = s1; )简单地将第二个variables赋值给第一个variables。 这导致第二个variables指向与第一个相同的对象实例。