Duplicating objects in Java

I learned that when you modify a variable in Java it doesn't change the variable it was based on

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

I assumed a similar thing for objects. Consider this class.

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;
}
}

After I tried this code I got confused.

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

Please explain to me why changing any of the objects affects the other one. I understand that the value of variable text is stored in the same place in memory for both of the objects.

Why the values for variables are independent but correlated for objects?

Also, how to duplicate SomeObject, if simple assignment does not do the job?

68631 次浏览

Second line (SomeObject s2 = s1;) simply assigns second variable to the first. This leads to second variable pointing to the same object instance as the first.

When you do this

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;

you have 2 references to the same object. Which means that whichever reference object you use, the changes you make will be visible when using the second reference.

Think of it like this: you have one television in the room, but two remotes: it doesn't matter which remote you use, you'll still be making changes to the same underlying object (the television).

When you write:

SomeObject s1 = new SomeObject("first");

s1 is not a SomeObject. It's a reference to the SomeObject object.

Hence if you assign s2 to s1, you're simply assigning references/handles and the underlying object is the same. This is important in Java. Everything is pass-by-value, but you never pass objects around - only references to objects.

So when you assign s1 = s2, and then call a method on s2 that changes an object, the underlying object is changed, and that's visible when you reference the object via s1.

This is one argument for immutability in objects. By making objects immutable they won't change under you and thus behave in a more predicatable fashion. If you want to copy an object, the easiest/most practical method is to write a copy() method that simply creates a new version and copies over the fields. You can do clever copies using serialisation/reflection etc. but that's more complex, obviously.

Every variable in Java is a reference. So when you do

SomeClass s2 = s1;

you just point s2 to the same object as s1 points to. You are actually assigning the value of the reference s1 (which points to an instance of SomeClass) to s2. If you modify ABC1, s2 will be modified as well (because it points to the same object).

There is an exception, primitive types: int, double, float, boolean, char, byte, short, long. They are stored by value. So when using =, you only assign the value, but they can not point to the same object (because they are not references). This means that

int b = a;

only sets the value of b to the value of a. If you change ABC1, b will not change.

At the end of the day, everything is assignment by value, it's just the value of the reference and not the value of the object (with the exception of primitive types as mentioned above).

So in your case, if you want to make a copy of s1, you can do it like this:

SomeClass s1 = new SomeClass("first");
SomeClass s2 = new SomeClass(s1.getText());

Alternatively, you can add a copy constructor to SomeClass that takes an instance as argument and copies it into its own instance.

class SomeClass {
private String text;
// all your fields and methods go here


public SomeClass(SomeClass copyInstance) {
this.text = new String(copyInstance.text);
}
}

With this you can copy an object pretty easily:

SomeClass s2 = new SomeClass(s1);

In your code s1 and s2 are the same object (you have only created one object using new) and you let s2 point to that same object in the next line. Therefore when you change text it changes both if you reference the value through s1 and s2.

The + operator on Integers creates a new object, it doesn't change the existing object (so adding 5+5 does not give 5 the new value 10...).

That is because s1 and s2 work only as references to your objects. When assigning s2 = s1 you only assign the reference, meaning that both will point to the same object in memory (the object that has the current text "first").

When you do a setter now, either on s1 or s2, both will modify the same object.

When you assign and object to a variable you really are assigning the reference to that object. So both variables s1 and s2 are referring the same object.

That's because the JVM stores a pointer to s1. When you call s2 = s1, you are basically saying that the s2 pointer (i.e. memory address) has the same value as the one for s1. Since they both point to the same place in memory, they represent exactly the same thing.

The = operator assigns pointer values. It does not copy the object.

Cloning objects is a complicated matter in itself. Every object has a clone() method which you might be tempted to use but it does a shallow copy (which basically means that it makes a copy of the top-level object but any object that are contained within it are not cloned). If you want to play around with object copies, definitely read Joshua Bloch's Effective Java.

Let's start with your second example:

This first statement assigns a new object to s1

SomeObject s1 = new SomeObject("first");

When you do the assignment in the second statement (SomeObject s2 = s1), you are telling s2 to point at the same object s1 is currently pointing at, so you have two references to the same object.

Note that you have not duplicated the SomeObject, rather two variables are just pointing to the same object. So if you are modify s1 or s2, you are actually modifying the same object (note if you did something like s2 = new SomeObject("second") they would now be pointing at different objects).

In your first example, a and b are primitive values, so modifying one will not affect the other.

Under the hood of Java, all objects do work using pass by value. For objects, you are passing the "value" you are passing is the object's location in memory (so it appears to have a similar effect of pass by reference). Primitives behave differently and just pass a copy of the value.

The answers above explain the behaviour you are seeing.

In answer to "Also, how to duplicate SomeObject, if simple assignment does not do the job?" - try searching for cloneable (it's a java interface that provides one way to copy objects) and 'copy constructors' (an alternative and, arguably, better approach)

Since objects were referenced by a reference. So if you write s2=s1 only the reference will copied. Its like in C were you only handle with memory addresses. And if you copy the address of an memory and change the value behind this address both pointers (references) will change the one value at this point in memory.

From Top Ten Errors Java Programmers Make:

6 - Confusion over passing by value, and passing by reference

This can be a frustrating problem to diagnose, because when you look at the code, you might be sure that its passing by reference, but find that its actually being passed by value. Java uses both, so you need to understand when you're passing by value, and when you're passing by reference.

When you pass a primitive data type, such as a char, int, float, or double, to a function then you are passing by value. That means that a copy of the data type is duplicated, and passed to the function. If the function chooses to modify that value, it will be modifying the copy only. Once the function finishes, and control is returned to the returning function, the "real" variable will be untouched, and no changes will have been saved. If you need to modify a primitive data type, make it a return value for a function, or wrap it inside an object.

Because int is a primitive type, int b = a; is a copy by value, which means that a and b are two different objects, but with the same value.

SomeObject s2 = s1; make s1 and s2 two references of a same object, so if you modify one, the other will be modified too.

A good solution is to implement another constructor like this:

public class SomeObject{


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


// your code


}

Then, use it like that:

SomeObject s2 = new SomeObject(s1);

Object assignment to a reference does not clone your object. References are like pointers. They point to an object and when operations are invoked it is done on the object pointed by the pointer. In your example, s1 and s2 are pointing to the same object and setters alter the state of the same object and the changes are visible across references.

Change you class in order to create new reference instead of using the same one:

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);
}
}

You can use something like this (I don't pretend to have ideal solution):

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;
}
}

Usage:

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

@brimborium's answer is very good (+1 for him), but I just want to elaborate more on it using some figures. Let's take the primitive assignment first:

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- First statement creates an Integer object of value 5. Then, at assigning it to the variable a, the Integer object will be unboxed and stored in a as a primitive.

After creating Integer object, and before assignment:

enter image description here

After assignment:

enter image description here

int b = a;

2- This will just read the value of a and then store it into b.

(The Integer object is now eligible for garbage collection, but not necessarily garbage-collected yet at this point)

enter image description here

b = b + b;

3- This reads the value of b twice, add them together, and place the new value into b.

enter image description here


On the other hand:

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- Creates a new instance of SomeObject class, and assigns it to the reference s1.

enter image description here

SomeObject s2 = s1;

2- This will make the reference s2 points to the object that s1 is pointing to.

enter image description here

s2.setText("second");

3- When you use setters on a reference, it will modify the object that the reference is pointing to.

enter image description here

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

4- Both should print second, since the two references s1 and s2 are referring to the same object (as shown in the previous figure).

int a = new Integer(5)

In the case above a new Integer is created. Here Integer is a non-primitive type and the value within it is converted (to an int) and assigned to an int 'a'.

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;

In this case both s1 and s2 are reference types. They are not created to contain a value like primitive types, rather they contain the reference of some object. For the sake of understanding we can think reference as a link which indicates where I can found the object being referenced.

Here the reference s1 tell us where we can found the value of "first" (which is actually stored in the memory of computer in an instance of SomeObject).In otherwords s1 is the address of the object of class "SomeObject". By the following assignment -

SomeObject s2 = s1;

we are just copying the value stored in s1 to s2 and we now know s1 contains the address of the string "first". After this assigenment both println() produces the same output because both s1 and s2 refrencing the same object.

Along with copy constructor you can copy object with clone() method if you are a java user. Clone can be used in the following manner -

SomeObject s3 = s1.clone();

For more information on clone() this is a useful link http://en.wikipedia.org/wiki/Clone_%28Java_method%29

T copyDeep(T input) {
return new Gson().fromJson(new Gson().toJson(input), T.class);
}