ProgrammingJava Developer

What is object copying in Java, how does cloning using the clone() method differ from copying through a constructor, and in what cases should each method be used?

Pass interviews with Hintsage AI assistant

Answer.

Object copying in Java is the process of creating a new object with the same state as an existing instance. Historically, copying became relevant at the very beginning of Java's development when the need arose to duplicate objects without manually reconstructing them. Java itself does not provide a universal way to copy objects; instead, it utilizes the contract of clone() and the copy constructor pattern.

History of the issue

The clone() method was introduced in the Cloneable interface to provide a standard mechanism for cloning objects; however, its implementation presents numerous nuances and often leads to bugs.

The problem

The main problem is the lack of true deep copying "out of the box" and the possibility of unpredictable behavior when performing shallow cloning. The copy constructor allows for both shallow and deep copying but requires explicit implementation.

Solution

For safe and correct copying of complex objects, it is preferable to use a copy constructor or factory method, while clone() should be used with caution—only if the object truly fits this contract.

Example code:

// Shallow cloning using clone() public class Address implements Cloneable { String city; public Address(String city) { this.city = city; } @Override public Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } } public class Person implements Cloneable { String name; Address address; public Person(String name, Address address) { this.name = name; this.address = address; } @Override public Person clone() throws CloneNotSupportedException { Person copy = (Person) super.clone(); copy.address = address.clone(); // deep copying address return copy; } } // Copy constructor public class Person { String name; Address address; public Person(Person other) { this.name = other.name; this.address = new Address(other.address.city); } }

Key features:

  • clone() requires the implementation of the Cloneable interface and overriding the clone method.
  • Shallow copying copies only references, not the objects they point to.
  • The copy constructor provides full control over the depth of copying and exception handling.

Trick questions.

Question 1: If a class does not implement Cloneable, what happens when clone() is called?

clone() will throw CloneNotSupportedException if the object does not implement the Cloneable interface.

Question 2: Is cloning via clone() deep by default?

No, by default only shallow cloning is implemented; all references to objects are copied as they are.

Question 3: Is it possible to do without a copy constructor for deep cloning?

No, if you want to control the process, you need to explicitly define the copying of nested objects either through a constructor or manually in clone().

Common mistakes and anti-patterns

  • Using shallow cloning for objects with mutable reference fields
  • Violating the clone contract (not calling super.clone)
  • Missing exception handling and inability to clone without implementing Cloneable

Real-life example

Negative case

A developer used the standard clone() in the Order class with a List<Product> field. As a result, when the list of products was changed in the copy, the products in the original were also modified, leading to a bug.

Pros:

  • Minimal code.
  • Fast cloning for simple objects.

Cons:

  • Serious bugs when copying objects with mutable fields.
  • Loss of control over the cloning process.

Positive case

The company implemented a copy constructor for all domain entities and additionally covered the cloning process for complex nested objects with tests.

Pros:

  • Full control over copying.
  • No unexpected side effects.

Cons:

  • Larger code volume.
  • Need to maintain the constructor when changing the class structure.