Как Java делает глубокую копию объекта?

Java задняя часть Apache Gson

Код реализации глубокого копирования: https://github.com/wudashan/java-deep-copy

вводить

В языке Java, когда нам нужно скопировать объект, существует два типа копирования: поверхностное копирование и глубокое копирование. Поверхностная копия копирует только адрес исходного объекта, поэтому при изменении значения исходного объекта значение копируемого объекта также изменяется. Глубокая копия заключается в копировании всех значений исходного объекта, поэтому даже если значение исходного объекта изменится, значение скопированного объекта не изменится. Описано ниже:

Поняв разницу между поверхностным и глубоким копированием, этот блог научит вас нескольким методам глубокого копирования.


копировать объект

Во-первых, давайте определим простой объект, который необходимо скопировать.

/**
 * 用户
 */
public class User {

    private String name;
    private Address address;

    // constructors, getters and setters

}

/**
 * 地址
 */
public class Address {

    private String city;
    private String country;

    // constructors, getters and setters

}

Как и в приведенном выше коде, мы определяем пользовательский класс User, включая имя и адрес, где адрес — это не строка, а другой класс Address, включая страну и город. Здесь опущены методы конструкторов и переменных-членов get() и set(). Далее мы подробно опишем, как выполнить глубокое копирование объекта User.


Метод 1 Конструктор

Мы можем сделать глубокую копию, вызвав конструктор, если формальный параметр базового типа и строки, то он будет присвоен напрямую, а если это объект, то будет создан новый.

прецедент

@Test
public void constructorCopy() {

    Address address = new Address("杭州", "中国");
    User user = new User("大山", address);

    // 调用构造函数时进行深拷贝
    User copyUser = new User(user.getName(), new Address(address.getCity(), address.getCountry()));

    // 修改源对象的值
    user.getAddress().setCity("深圳");

    // 检查两个对象的值不同
    assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());

}

Метод 2 Перегрузить метод clone()

Родительский класс Object имеет метод копирования clone(), но это защищенный тип, нам нужно переопределить его и изменить на общедоступный тип. Кроме того, подклассы также должны реализовать интерфейс Cloneable, чтобы сообщить JVM, что класс можно скопировать.

переписать код

Давайте изменим класс User и класс Address, чтобы реализовать интерфейс Cloneable для поддержки глубокого копирования.

/**
 * 地址
 */
public class Address implements Cloneable {

    private String city;
    private String country;

    // constructors, getters and setters

    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }

}
/**
 * 用户
 */
public class User implements Cloneable {

    private String name;
    private Address address;

    // constructors, getters and setters

    @Override
    public User clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        user.setAddress(this.address.clone());
        return user;
    }

}

должны знать о том,super.clone()На самом деле это неглубокая копия, поэтому при переписывании метода clone() класса User объект адреса должен вызыватьaddress.clone()переназначить.

прецедент

@Test
public void cloneCopy() throws CloneNotSupportedException {

    Address address = new Address("杭州", "中国");
    User user = new User("大山", address);

    // 调用clone()方法进行深拷贝
    User copyUser = user.clone();

    // 修改源对象的值
    user.getAddress().setCity("深圳");

    // 检查两个对象的值不同
    assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());

}

Метод 3 Сериализация Apache Commons Lang

Java предоставляет возможность сериализации, мы можем сначала сериализовать исходный объект, а затем десериализовать для создания объекта-копии. Однако предпосылка использования сериализации заключается в том, что скопированный класс (включая его переменные-члены) должен реализовать интерфейс Serializable. Пакет Apache Commons Lang инкапсулирует сериализацию Java, и мы можем использовать его напрямую.

переписать код

Давайте изменим класс User и класс Address, чтобы реализовать интерфейс Serializable для поддержки сериализации.

/**
 * 地址
 */
public class Address implements Serializable {

    private String city;
    private String country;

    // constructors, getters and setters

}
/**
 * 用户
 */
public class User implements Serializable {

    private String name;
    private Address address;

    // constructors, getters and setters

}

прецедент

@Test
public void serializableCopy() {

    Address address = new Address("杭州", "中国");
    User user = new User("大山", address);

    // 使用Apache Commons Lang序列化进行深拷贝
    User copyUser = (User) SerializationUtils.clone(user);

    // 修改源对象的值
    user.getAddress().setCity("深圳");

    // 检查两个对象的值不同
    assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());

}

Метод 4 Gson сериализации

Gson может сериализовать объекты в JSON, а также может десериализовать JSON в объекты, поэтому мы можем использовать его для глубокого копирования.

прецедент

@Test
public void gsonCopy() {

    Address address = new Address("杭州", "中国");
    User user = new User("大山", address);

    // 使用Gson序列化进行深拷贝
    Gson gson = new Gson();
    User copyUser = gson.fromJson(gson.toJson(user), User.class);

    // 修改源对象的值
    user.getAddress().setCity("深圳");

    // 检查两个对象的值不同
    assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());

}

Метод пятой сериализации Джексона

Jackson похож на Gson тем, что может сериализовать объекты в JSON.Очевидная разница в том, что скопированный класс (включая его переменные-члены) должен иметь конструктор по умолчанию без аргументов.

переписать код

Давайте изменим класс User, класс Address и реализуем конструктор без аргументов по умолчанию для поддержки Джексона.

/**
 * 用户
 */
public class User {

    private String name;
    private Address address;

    // constructors, getters and setters

    public User() {
    }

}

/**
 * 地址
 */
public class Address {

    private String city;
    private String country;

    // constructors, getters and setters

    public Address() {
    }

}

прецедент

@Test
public void jacksonCopy() throws IOException {

    Address address = new Address("杭州", "中国");
    User user = new User("大山", address);

    // 使用Jackson序列化进行深拷贝
    ObjectMapper objectMapper = new ObjectMapper();
    User copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), User.class);

    // 修改源对象的值
    user.getAddress().setCity("深圳");

    // 检查两个对象的值不同
    assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity());

}

Суммировать

Сказав так много методов реализации глубокого копирования, какой метод является лучшим? Простейшее суждение состоит в том, чтобы сделать выбор в зависимости от того, предоставляет ли скопированный класс (включая его переменные-члены) конструктор глубокого копирования, реализует ли он интерфейс Cloneable, реализует ли он интерфейс Serializable и реализует ли он конструктор без аргументов по умолчанию. Для детального рассмотрения вы можете обратиться к таблице ниже:

метод глубокого копирования преимущество недостаток
Конструктор 1. Базовая реализация проста
2. Нет необходимости внедрять сторонние пакеты
3. Низкие системные накладные расходы
4. Нет требований к классу копирования, нет необходимости реализовывать дополнительные интерфейсы и методы
1. Плохое удобство использования, каждый раз, когда вы добавляете переменную-член, вам нужно добавить новый конструктор копирования.
Перегрузить метод clone() 1. Базовая реализация относительно проста
2. Нет необходимости внедрять сторонние пакеты
3. Низкие системные накладные расходы
1. Плохое удобство использования, вам может потребоваться модифицировать метод clone() каждый раз, когда вы добавляете переменную-член.
2. Класс копирования (включая его переменные-члены) должен реализовать интерфейс Cloneable.
Сериализация Apache Commons Lang 1. Удобство использования, новые переменные-члены не требуют изменения метода копирования. 1. Базовая реализация сложнее
2. Необходимо представить сторонний JAR-пакет Apache Commons Lang.
3. Класс копирования (включая его переменные-члены) должен реализовать интерфейс Serializable.
4. При сериализации и десериализации возникают определенные системные накладные расходы.
Сериализация Gson 1. Удобство использования, новые переменные-члены не требуют изменения метода копирования.
2. Нет требований к классу копирования, нет необходимости реализовывать дополнительные интерфейсы и методы
1. Базовая реализация сложна
2. Необходимо представить сторонний JAR-пакет Gson.
3. При сериализации и десериализации возникают определенные системные накладные расходы.
сериализация Джексона 1. Удобство использования, новые переменные-члены не требуют изменения метода копирования. 1. Базовая реализация сложна
2. Необходимо представить сторонний JAR-пакет Jackson.
3. Класс копирования (включая его переменные-члены) должен реализовать конструктор без аргументов по умолчанию.
4. При сериализации и десериализации возникают определенные системные накладные расходы.

эталонное чтение

[1] How to Make a Deep Copy of an Object in Java