Understanding Object Cloning in Java: Shallow vs Deep Copy
This article explains Java object cloning, detailing why cloning is needed, how to implement shallow and deep copies using the Cloneable interface and serialization, and provides comprehensive code examples illustrating the differences and pitfalls of reference copying.
Java object cloning is a fundamental technique for creating copies of objects without using the new operator. This article walks through the motivation for cloning, the pitfalls of simple reference assignment, and two main cloning strategies: shallow copy and deep copy.
Why Clone?
Cloning is useful when a new object must preserve the current state of an existing object, especially when the original object has already been modified. Simple assignment copies only the reference, causing both variables to point to the same memory location.
How to Implement Cloning
Java provides the protected native method Object.clone() in java.lang.Object . To make a class cloneable, it must implement the marker interface Cloneable and override clone() with a public method that calls super.clone() .
Shallow Clone
A shallow clone copies the object's primitive fields and the references of its object fields. The referenced objects themselves are not duplicated, so both the original and the clone share the same sub‑objects.
class Student implements Cloneable {
private int number;
public int getNumber() { return number; }
public void setNumber(int number) { this.number = number; }
@Override
public Object clone() {
Student stu = null;
try { stu = (Student) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
return stu;
}
}
public class Test {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student) stu1.clone();
System.out.println("stu1 == stu2: " + (stu1 == stu2)); // false
}
}The output shows that stu1 and stu2 are different objects, but if Student contained a reference field, both would still point to the same instance.
Deep Clone
Deep cloning duplicates not only the object itself but also all objects it references, recursively. This can be achieved by manually cloning each reference field inside the overridden clone() method, or by using serialization.
class Address implements Cloneable {
private String add;
public String getAdd() { return add; }
public void setAdd(String add) { this.add = add; }
@Override
public Object clone() {
Address addr = null;
try { addr = (Address) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
return addr;
}
}
class Student implements Cloneable {
private int number;
private Address addr;
public int getNumber() { return number; }
public void setNumber(int number) { this.number = number; }
public Address getAddr() { return addr; }
public void setAddr(Address addr) { this.addr = addr; }
@Override
public Object clone() {
Student stu = null;
try { stu = (Student) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); }
stu.addr = (Address) addr.clone(); // deep copy of address
return stu;
}
}
public class Test {
public static void main(String[] args) {
Address addr = new Address();
addr.setAdd("Hangzhou");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student) stu1.clone();
System.out.println("Student1:" + stu1.getNumber() + ", address:" + stu1.getAddr().getAdd());
System.out.println("Student2:" + stu2.getNumber() + ", address:" + stu2.getAddr().getAdd());
// modify original address
addr.setAdd("Xihu");
System.out.println("After modification:");
System.out.println("Student1:" + stu1.getNumber() + ", address:" + stu1.getAddr().getAdd());
System.out.println("Student2:" + stu2.getNumber() + ", address:" + stu2.getAddr().getAdd());
}
}Running the program shows that changing the address of the original object does not affect the cloned object's address, confirming a true deep copy.
Deep Clone via Serialization
When an object graph is complex, manually cloning each nested object becomes cumbersome. Serializing the object to a byte stream and then deserializing it creates a deep copy automatically, provided all involved classes implement Serializable .
public class Outer implements Serializable {
public Inner inner;
public Outer myclone() {
Outer outer = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }
return outer;
}
}
class Inner implements Serializable {
public String name = "";
public Inner(String name) { this.name = name; }
@Override
public String toString() { return "Inner name: " + name; }
}This method ensures that the cloned object and all its nested objects are completely independent.
Summary
Object cloning in Java can be achieved either by implementing Cloneable and overriding clone() for shallow or manually deep copies, or by using serialization for a reliable deep clone. The latter approach also provides compile‑time checks for serializability, making it a safer alternative to the native clone() method.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.