Creating deep copies in Java

3 min read
Guide on
Table of contents

The need to create copies is pretty common in programming. In a nutshell, you may need the copy of an object’s reference (called a shallow copy) or the copy of the object’s data (called a deep copy), depending on the requirement. Java makes it pretty straightforward to create shallow copies of an object; you need to implement the Cloneable interface on its class and override the clone() method. However, there are no in-built mechanisms to create deep copies of an object in Java. In this guide, we’ll explore some ways to do this for different usecases.

Manually copying the data

One way to achieve a predictable deep copying mechanism is to manually implement it in the classes. You can do this by defining the following interface.

java
public interface Copyable<T> {

  T copy();
}

You can implement this interface in the classes that need to provide deep copies.

java
public class Book implements Copyable<Book> {

  private String title;
  private Author author;

  @Override
  public Book copy() {
    final Book newBook = new Book();
    newBook.setAuthor(author.copy());
    newBook.setTitle(title);
    return newBook;
  }

  // getters, setters, etc
}

public class Author implements Copyable<Author> {

  private String name;

  @Override
  public Author copy() {
    final Author newAuthor = new Author();
    newAuthor.setName(name);
    return newAuthor;
  }

  // getters, setters, etc
}

You can now verify that calling the copy() method does create a deep copy through the following test.

java
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class BookDeepCopyTest {

  @Test
  @DisplayName("Should copy data but not reference")
  void shouldCopyDataButNotReference() {
    final var andyWeir = new Author();
    andyWeir.setName("Andy Weir");

    final var martian = new Book();
    martian.setTitle("The Martian");
    martian.setAuthor(andyWeir);

    final Book copyOfMartian = martian.copy();

    assertThat(copyOfMartian).isNotEqualTo(martian);
    assertThat(copyOfMartian.getAuthor()).isNotEqualTo(martian.getAuthor());
    assertThat(copyOfMartian.getTitle()).isEqualTo(martian.getTitle());
    assertThat(copyOfMartian.getAuthor().getName()).isEqualTo(martian.getAuthor().getName());

    copyOfMartian.getAuthor().setName("Marie Lu");
    assertThat(copyOfMartian.getAuthor().getName()).isNotEqualTo(martian.getAuthor().getName());
  }
}

At the end of the test, you can see that even if we modify the author’s name in the copy of a Book object, the original object remains unaffected.

There are a few caveats in this approach.

Copying with serialized representation to object

A better approach to generate a deep copy is by serialization using a Java library. There are multiple ways to do this; one of them is to serialize the object to JSON using Jackson and deserialize it.

Assuming you’ve got a Maven project, add the jackson-databind dependency in pom.xml.

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
  <!-- rest of the pom -->

  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.3</version>
    </dependency>
  </dependencies>

</project>

You won’t need to modify the original classes in this case.

java
import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class BookDeepCopyWithJacksonTest {

  @Test
  @DisplayName("Should copy data but not reference")
  void shouldCopyDataButNotReference() throws JsonProcessingException {
    final var andyWeir = new Author();
    andyWeir.setName("Andy Weir");

    final var martian = new Book();
    martian.setTitle("The Martian");
    martian.setAuthor(andyWeir);

    final var objectMapper = new ObjectMapper();
    final String json = objectMapper.writeValueAsString(martian);
    final Book copyOfMartian = objectMapper.readValue(json, Book.class);

    assertThat(copyOfMartian).isNotEqualTo(martian);
    assertThat(copyOfMartian.getAuthor()).isNotEqualTo(martian.getAuthor());
    assertThat(copyOfMartian.getTitle()).isEqualTo(martian.getTitle());
    assertThat(copyOfMartian.getAuthor().getName()).isEqualTo(martian.getAuthor().getName());

    copyOfMartian.getAuthor().setName("Marie Lu");
    assertThat(copyOfMartian.getAuthor().getName()).isNotEqualTo(martian.getAuthor().getName());
  }
}

In the above test, to create a deep copy, we

Using this approach

However, note that this is a two-step process and can cause performance issues in some scenarios where memory is limited and objects are massive.

Copying from object to object

It is possible to avoid the two-step process used above and directly copy the object. You can use a serialization framework called Kryo which can create deep copies by direct copying data from the object to object.

Assuming you have a Maven project, add the kryo dependency in pom.xml.

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
  <!-- rest of the pom -->

  <dependencies>
    <dependency>
      <groupId>com.esotericsoftware</groupId>
      <artifactId>kryo</artifactId>
      <version>5.1.1</version>
    </dependency>
  </dependencies>

</project>

As in the previous approach, you won’t need to do any changes to the source code of the classes.

java
import static org.assertj.core.api.Assertions.assertThat;

import com.esotericsoftware.kryo.Kryo;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class BookDeepCopyWithKryoTest {

  @Test
  @DisplayName("Should copy data but not reference")
  void shouldCopyDataButNotReference() {
    final var andyWeir = new Author();
    andyWeir.setName("Andy Weir");

    final var martian = new Book();
    martian.setTitle("The Martian");
    martian.setAuthor(andyWeir);

    final var kryo = new Kryo();
    kryo.setRegistrationRequired(false);
    final Book copyOfMartian = kryo.copy(martian);

    assertThat(copyOfMartian).isNotEqualTo(martian);
    assertThat(copyOfMartian.getAuthor()).isNotEqualTo(martian.getAuthor());
    assertThat(copyOfMartian.getTitle()).isEqualTo(martian.getTitle());
    assertThat(copyOfMartian.getAuthor().getName()).isEqualTo(martian.getAuthor().getName());

    copyOfMartian.getAuthor().setName("Marie Lu");
    assertThat(copyOfMartian.getAuthor().getName()).isNotEqualTo(martian.getAuthor().getName());
  }
}

In the test above, we

This method is a single-step process, and much faster than previous approaches. It also works pretty effectively with complex / massive objects.

References
Share