Java Notes
Complete guide to object cloning in Java — Cloneable interface, shallow vs deep cloning, clone() method, copy constructors, and serialization-based cloning.
Object cloning is the process of creating an exact copy of an existing object. The clone is a new object in memory with the same state as the original — modifying the clone doesn't affect the original (if done correctly).
Java provides the clone() method in the Object class, but it comes with caveats and pitfalls that every Java developer must understand.
The clone() Method and Cloneable Interface
To use clone(), a class must:
- Implement the
Cloneablemarker interface - Override the
clone()method (change fromprotectedtopublic)
public class Student implements Cloneable {
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.gpa = gpa;
}
@Override
public Student clone() {
try {
return (Student) super.clone(); // Object.clone() does field-by-field copy
} catch (CloneNotSupportedException e) {
throw new AssertionError("Should not happen - we implement Cloneable");
}
}
@Override
public String toString() {
return name + " (age=" + age + ", GPA=" + gpa + ")";
}
public void setName(String name) { this.name = name; }
public void setGpa(double gpa) { this.gpa = gpa; }
}
public class Main {
public static void main(String[] args) {
Student original = new Student("Alice", 20, 3.8);
Student copy = original.clone();
System.out.println("Original: " + original);
System.out.println("Copy: " + copy);
System.out.println("Same object? " + (original == copy));
// Modify copy — original unaffected
copy.setName("Bob");
copy.setGpa(3.5);
System.out.println("\nAfter modifying copy:");
System.out.println("Original: " + original);
System.out.println("Copy: " + copy);
}
}Original: Alice (age=20, GPA=3.8) Copy: Alice (age=20, GPA=3.8) Same object? false After modifying copy: Original: Alice (age=20, GPA=3.8) Copy: Bob (age=20, GPA=3.5)
Without Cloneable — CloneNotSupportedException
public class NotCloneable {
private int value;
public NotCloneable(int value) { this.value = value; }
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // Will throw because class doesn't implement Cloneable!
}
}
public class Main {
public static void main(String[] args) {
NotCloneable obj = new NotCloneable(42);
try {
obj.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Cannot clone: " + e.getMessage());
}
}
}Cannot clone: NotCloneable
Shallow Clone — The Default Behavior
Object.clone() performs a shallow copy — it copies field values directly. For primitive fields, this works perfectly. For reference fields, only the reference is copied (both original and clone point to the same object).
public class Address {
String city;
String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
@Override
public String toString() { return street + ", " + city; }
}
public class Person implements Cloneable {
private String name;
private int age;
private Address address; // Reference type!
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Person clone() {
try {
return (Person) super.clone(); // Shallow clone!
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
@Override
public String toString() {
return name + ", " + age + " @ " + address;
}
}
public class Main {
public static void main(String[] args) {
Person original = new Person("Alice", 30, new Address("NYC", "123 Main"));
Person shallowCopy = original.clone();
System.out.println("Original: " + original);
System.out.println("Copy: " + shallowCopy);
// ⚠️ PROBLEM: Modifying copy's address affects original!
shallowCopy.address.city = "Los Angeles";
System.out.println("\nAfter changing copy's city:");
System.out.println("Original: " + original); // ALSO CHANGED!
System.out.println("Copy: " + shallowCopy);
}
}Original: Alice, 30 @ 123 Main, NYC Copy: Alice, 30 @ 123 Main, NYC After changing copy's city: Original: Alice, 30 @ 123 Main, Los Angeles Copy: Alice, 30 @ 123 Main, Los Angeles
This is the shallow clone trap — the Address is shared between original and copy.
Deep Clone — Copying Everything
A deep clone recursively clones all mutable reference fields:
public class Address implements Cloneable {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public void setCity(String city) { this.city = city; }
@Override
public String toString() { return street + ", " + city; }
}
public class Person implements Cloneable {
private String name;
private int age;
private Address address;
private List<String> hobbies;
public Person(String name, int age, Address address, List<String> hobbies) {
this.name = name;
this.age = age;
this.address = address;
this.hobbies = new ArrayList<>(hobbies);
}
@Override
public Person clone() {
try {
Person copy = (Person) super.clone();
// Deep clone mutable references
copy.address = this.address.clone();
copy.hobbies = new ArrayList<>(this.hobbies);
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public Address getAddress() { return address; }
public List<String> getHobbies() { return hobbies; }
@Override
public String toString() {
return name + ", " + age + " @ " + address + " | Hobbies: " + hobbies;
}
}
public class Main {
public static void main(String[] args) {
Person original = new Person("Alice", 30,
new Address("NYC", "123 Main St"),
List.of("Reading", "Swimming"));
Person deepCopy = original.clone();
// Modify the copy
deepCopy.getAddress().setCity("San Francisco");
deepCopy.getHobbies().add("Coding");
System.out.println("Original: " + original);
System.out.println("Copy: " + deepCopy);
// Original is UNAFFECTED!
}
}Original: Alice, 30 @ 123 Main St, NYC | Hobbies: [Reading, Swimming] Copy: Alice, 30 @ 123 Main St, San Francisco | Hobbies: [Reading, Swimming, Coding]
Copy Constructor Alternative (Recommended)
Many experts recommend copy constructors over clone():
public class Employee {
private String name;
private String department;
private List<String> skills;
private Address address;
// Regular constructor
public Employee(String name, String department, Address address, List<String> skills) {
this.name = name;
this.department = department;
this.address = address;
this.skills = new ArrayList<>(skills);
}
// Copy constructor — clear, explicit, no Cloneable needed
public Employee(Employee other) {
this.name = other.name;
this.department = other.department;
this.address = new Address(other.address); // Deep copy
this.skills = new ArrayList<>(other.skills); // Deep copy
}
// Static factory method alternative
public static Employee copyOf(Employee other) {
return new Employee(other);
}
@Override
public String toString() {
return name + " (" + department + ") - Skills: " + skills;
}
public void addSkill(String skill) { skills.add(skill); }
}
public class Main {
public static void main(String[] args) {
Employee original = new Employee("Alice", "Engineering",
new Address("NYC", "Tech St"),
List.of("Java", "Python"));
Employee copy = new Employee(original); // Copy constructor
copy.addSkill("Go");
System.out.println("Original: " + original);
System.out.println("Copy: " + copy);
}
}Original: Alice (Engineering) - Skills: [Java, Python] Copy: Alice (Engineering) - Skills: [Java, Python, Go]
Cloning via Serialization (Deep Clone Any Object)
For complex object graphs, serialization provides automatic deep cloning:
import java.io.*;
public class DeepCopyUtil {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("Deep copy failed", e);
}
}
}
// All classes in the graph must be Serializable
public class Department implements Serializable {
private String name;
private List<String> members;
public Department(String name, List<String> members) {
this.name = name;
this.members = new ArrayList<>(members);
}
public void addMember(String member) { members.add(member); }
@Override
public String toString() { return name + ": " + members; }
}
public class Main {
public static void main(String[] args) {
Department original = new Department("Engineering",
new ArrayList<>(List.of("Alice", "Bob")));
Department copy = DeepCopyUtil.deepCopy(original);
copy.addMember("Charlie");
System.out.println("Original: " + original);
System.out.println("Copy: " + copy);
}
}Original: Engineering: [Alice, Bob] Copy: Engineering: [Alice, Bob, Charlie]
Shallow vs Deep Clone Comparison
| Aspect | Shallow Clone | Deep Clone |
|---|---|---|
| Primitive fields | Copied ✅ | Copied ✅ |
| Reference fields | Reference copied (shared) | Object cloned (independent) |
| Performance | Fast | Slower (recursive) |
| Complexity | Simple | Must clone all mutable refs |
| Safety | Dangerous if mutated | Safe |
| Default behavior | Object.clone() | Must implement manually |
Prototype Design Pattern
Cloning is the basis of the Prototype pattern — creating new objects by copying a prototype:
public abstract class Shape implements Cloneable {
protected String type;
protected String color;
public abstract void draw();
@Override
public Shape clone() {
try {
return (Shape) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class CircleShape extends Shape {
private double radius;
public CircleShape(double radius, String color) {
this.type = "Circle";
this.radius = radius;
this.color = color;
}
@Override
public void draw() {
System.out.println("Drawing " + color + " circle (r=" + radius + ")");
}
}
// Cache of prototypes
public class ShapeRegistry {
private static Map<String, Shape> prototypes = new HashMap<>();
static {
prototypes.put("red-circle", new CircleShape(5.0, "red"));
prototypes.put("blue-circle", new CircleShape(10.0, "blue"));
}
public static Shape getShape(String key) {
return prototypes.get(key).clone(); // Return a COPY, not the original
}
}
public class Main {
public static void main(String[] args) {
Shape s1 = ShapeRegistry.getShape("red-circle");
Shape s2 = ShapeRegistry.getShape("red-circle");
s1.draw();
s2.draw();
System.out.println("Same object? " + (s1 == s2)); // false — independent copies
}
}Drawing red circle (r=5.0) Drawing red circle (r=5.0) Same object? false
Common Mistakes
- Forgetting to implement Cloneable:
``java class MyClass { // No implements Cloneable! public Object clone() { return super.clone(); } // Throws at runtime! } ``
- Shallow clone with mutable references:
``java // Only calling super.clone() when you have List, Date, or custom objects // Result: Original and clone share mutable state ``
- Not handling CloneNotSupportedException:
``java // Always wrap in try-catch or declare throws ``
- Assuming String needs deep cloning:
``java // Strings are IMMUTABLE — shallow copy is fine for String fields // No need to: copy.name = new String(this.name); ``
Best Practices
- Prefer copy constructors over
clone()— they're more explicit and type-safe - If using
clone(), always implement deep clone for mutable reference fields - Make
clone()return the specific type (covariant return) - Document whether your clone is shallow or deep
- Consider immutability — immutable objects never need cloning
- Use serialization-based deep copy for complex object graphs
- For Java 14+, consider Records for simple value objects
Interview Questions
Q1: What is the purpose of the Cloneable interface? It's a marker interface that tells Object.clone() it's OK to copy this object. Without it, clone() throws CloneNotSupportedException.
Q2: What is the difference between shallow and deep cloning? Shallow: copies field values as-is (references point to same objects). Deep: recursively creates new copies of all mutable referenced objects.
Q3: Why is clone() in the Object class and not in Cloneable? Historical design decision. Object.clone() does native memory-level copy. The Cloneable interface is just a permission flag. Many consider this design flawed.
Q4: Can clone() be called on arrays? Yes. Arrays implement Cloneable implicitly. int[] copy = original.clone() works. But for object arrays, it's a shallow clone.
Q5: What are alternatives to clone()? Copy constructor, static factory method (copyOf()), serialization-based deep copy, and manual copying. Copy constructors are generally preferred.
Q6: Is String cloning necessary? No. Strings are immutable — sharing references to the same String is perfectly safe. No deep clone needed.
Q7: What is the Prototype pattern? A creational design pattern where new objects are created by cloning a prototype instance, instead of calling a constructor. Useful when object creation is expensive.
Q8: What is the Prototype design pattern and how does it relate to cloning? Prototype pattern creates new objects by copying existing ones instead of instantiation. When object creation is expensive (DB calls, complex setup), cloning a prototype is faster. Java's clone() supports this pattern directly. Used in object pools and caching systems.
Q9: How can you deep clone an object using serialization? Serialize the object to a byte stream, then deserialize it back to create a completely independent copy. All referenced objects are also serialized/deserialized, creating true deep copies. Downside: all classes in the graph must be Serializable, and it's slower than manual deep copy.
Q10: What is the difference between clone() and copy constructor? clone(): requires Cloneable, uses Object.clone(), returns Object (needs casting), problematic with final fields. Copy constructor: regular constructor taking same type as parameter, no interface needed, type-safe, works with final fields. Copy constructors are generally preferred.
Q11: Can you clone an array in Java? Yes. Arrays implement Cloneable by default. int[] copy = original.clone() creates a shallow copy. For primitive arrays, this is effectively a deep copy. For object arrays, it copies references — the objects themselves aren't cloned.
Q12: What happens if you call clone() without implementing Cloneable? CloneNotSupportedException is thrown at runtime. The Object.clone() method checks if the object's class implements Cloneable. If not, it throws the exception. This is the marker interface pattern in action.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Object Cloning in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, oops, object, cloning
Related Java Master Course Topics