Код реализации глубокого копирования: 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