在Java中复制对象

时间:2012-08-22 12:04:19

标签: java variables object pointers reference

我了解到,当您在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?

16 个答案:

答案 0 :(得分:128)

Java中的每个变量都是引用。所以当你这样做时

SomeClass s2 = s1;

您只需将s2指向与s1指向的同一对象即可。实际上,您正在将引用s1的值(指向SomeClass的实例)分配给s2。 如果修改s1s2也会被修改(因为它指向同一个对象)。

有一个例外,原始类型:int, double, float, boolean, char, byte, short, long。它们按值存储。因此,在使用=时,您只需指定值,但它们不能指向同一个对象(因为它们不是引用)。这意味着

int b = a;

仅将b的值设置为a的值。 如果您更改ab将不会更改。

在一天结束时,一切都是按值分配,它只是引用的值而不是对象的值(除了上面提到的基本类型)。

因此,在您的情况下,如果您想复制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)


@ 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-第一个语句创建一个值为5的Integer对象。然后,在将其分配给变量a时,Integer对象将被取消装箱并作为基元存储在a中。

创建Integer对象后,在赋值之前:

enter image description here

转让后:

enter image description here

int b = a;

2-这只会读取a的值,然后将其存储到b

(整数对象现在有资格进行垃圾回收,但此时不一定是垃圾回收)

enter image description here

b = b + b;

3-这会将b的值读取两次,将它们加在一起,然后将新值放入b

enter image description here


另一方面:

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

enter image description here

SomeObject s2 = s1;

2-这将使引用s2指向s1指向的对象。

enter image description here

s2.setText("second");

3-在引用上使用setter时,它将修改引用指向的对象。

enter image description here

System.out.println(s1.getText());
System.out.println(s2.getText());

4-两者都应打印second,因为两个引用s1s2指的是同一个对象(如上图所示)。

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

SomeObject s2 = s1;使s1s2对同一个对象进行两次引用,因此如果您修改了一个,则另一个也会被修改。

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

public class SomeObject{

    public SomeObject(SomeObject someObject) {
        setText(someObject.getText());
    }

    // your code

}

然后,使用它:

SomeObject s2 = new SomeObject(s1);

答案 5 :(得分:2)

在您的代码中s1s2相同的对象(您只使用new创建了一个对象),然后让s2指向下一行中的同一个对象。因此,当您更改text时,如果您通过s1s2引用该值,则会更改两者。

整数上的+运算符创建一个 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,而是两个变量只指向同一个对象。因此,如果您要修改s1s2,则实际上是在修改同一个对象(请注意,如果您执行类似s2 = new SomeObject("second")的操作,他们现在会指向不同的对象)。

在您的第一个示例中,ab是原始值,因此修改一个不会影响另一个。

在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)

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

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

答案 14 :(得分:1)

当您为变量赋值和对象时,您确实正在为该对象分配引用。因此,变量s1s2都指的是同一个对象。

答案 15 :(得分:1)

由于引用引用了对象。因此,如果您编写s2 = s1,则仅复制引用。就像在C中一样,你只处理内存地址。如果你复制一个内存的地址并改变这个地址后面的值,两个指针(引用)将改变内存中此时的一个值。