Трохи складно реалізувати функцію копіювання глибокого об'єкта. Які кроки ви робите, щоб забезпечити вихідний об'єкт і клоновану поділитися без посилання?
Трохи складно реалізувати функцію копіювання глибокого об'єкта. Які кроки ви робите, щоб забезпечити вихідний об'єкт і клоновану поділитися без посилання?
Відповіді:
Безпечним способом є серіалізація об’єкта, а потім десеріалізація. Це забезпечує все абсолютно нове посилання.
Ось стаття про те, як це зробити ефективно.
Caveats: Класи можуть переосмислити серіалізацію таким чином, щоб нові екземпляри не створювалися, наприклад для одиночних клавіш. Крім того, це, звичайно, не працює, якщо ваші заняття не є серіалізаційними.
Кілька людей згадували про використання або переосмислення Object.clone()
. Не робіть цього. Object.clone()
має деякі основні проблеми, і його використання в більшості випадків не рекомендується. Щоб отримати повну відповідь, див. Пункт 11, " Ефективна Java " Джошуа Блоха. Я вважаю, що ви можете спокійно використовувати Object.clone()
масиви примітивного типу, але крім цього вам потрібно бути розумним щодо правильного використання та перекриття клону.
Схеми, які покладаються на серіалізацію (XML чи іншим чином), є химерними.
Тут немає простої відповіді. Якщо ви хочете глибоко скопіювати об'єкт, вам доведеться пройти графік об'єкта та скопіювати кожен дочірній об'єкт явно через конструктор копій об'єкта або статичний заводський метод, який у свою чергу глибоко копіює дочірній об’єкт. Незмінні String
копії (наприклад, s) не потрібно копіювати. Як осторонь, ви повинні віддати перевагу незмінності з цієї причини.
Ви можете зробити глибоку копію з серіалізацією без створення файлів.
Об'єкт, який ви бажаєте скопіювати, потрібно буде implement serializable
. Якщо клас не є остаточним або його неможливо змінити, розширіть клас та застосуйте серіалізацію.
Перетворіть свій клас у потік байтів:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();
Відновіть свій клас із потоку байтів:
ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();
instance
у цьому випадку?
Ви можете зробити глибокий клон на основі серіалізації, використовуючи org.apache.commons.lang3.SerializationUtils.clone(T)
Apache Commons Lang, але будьте обережні - продуктивність є безглуздою.
Загалом, найкраще застосовувати власні методи клонування для кожного класу об'єкта в графіку об'єкта, що потребує клонування.
org.apache.commons.lang.SerializationUtils
Один із способів реалізації глибокої копії - додавання конструкторів копій до кожного асоційованого класу. Конструктор копій бере екземпляр "цього" як єдиний аргумент і копіює з нього всі значення. Досить певної роботи, але досить прямої та безпечної.
EDIT: зауважте, що для читання полів вам не потрібно використовувати методи аксесуара. Ви можете отримати доступ до всіх полів безпосередньо, тому що вихідний екземпляр завжди того ж типу, що і екземпляр із конструктором копій. Очевидне, але може бути не помічене.
Приклад:
public class Order {
private long number;
public Order() {
}
/**
* Copy constructor
*/
public Order(Order source) {
number = source.number;
}
}
public class Customer {
private String name;
private List<Order> orders = new ArrayList<Order>();
public Customer() {
}
/**
* Copy constructor
*/
public Customer(Customer source) {
name = source.name;
for (Order sourceOrder : source.orders) {
orders.add(new Order(sourceOrder));
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Редагувати: Зауважте, що при використанні конструкторів копій необхідно знати тип виконання об'єкта, який ви копіюєте. При наведеному вище підході ви не можете легко скопіювати змішаний список (можливо, ви зможете зробити це за допомогою коду відображення).
Toyota
, ваш код буде вставлений у список Car
призначення. Правильне клонування зазвичай вимагає, щоб клас надавав метод віртуальної фабрики, у контракті якого зазначено, що він поверне новий об'єкт власного класу; сам кондуктор копії повинен бути protected
таким, щоб він використовувався лише для побудови об'єктів, точний тип яких відповідає об'єкту, який копіюється).
Можна використовувати бібліотеку, яка має простий API та виконує відносно швидке клонування з відображенням (має бути швидше, ніж методи серіалізації).
Cloner cloner = new Cloner();
MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o
Apache commons пропонує швидкий спосіб глибокого клонування предмета.
My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
XStream дійсно корисний у таких випадках. Ось простий код зробити клонування
private static final XStream XSTREAM = new XStream();
...
Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
Один дуже простий і простий підхід - використовувати Jackson JSON для серіалізації складного Java Object до JSON та його читання.
Для користувачів Spring Framework . Використання класу org.springframework.util.SerializationUtils
:
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}
Для складних об'єктів і коли продуктивність не значна, я використовую бібліотеку json, як-от gson, щоб серіалізувати об’єкт у json-текст, а потім деріаріалізувати текст, щоб отримати новий об’єкт.
gson, який базується на відображенні, працюватиме в більшості випадків, за винятком того, що transient
поля не будуть скопійовані та об'єкти з круговою посиланням із причиною StackOverflowError
.
public static <T> T copy(T anObject, Class<T> classInfo) {
Gson gson = new GsonBuilder().create();
String text = gson.toJson(anObject);
T newObject = gson.fromJson(text, classInfo);
return newObject;
}
public static void main(String[] args) {
String originalObject = "hello";
String copiedObject = copy(originalObject, String.class);
}
Використовуйте XStream ( http://x-stream.github.io/ ). Ви навіть можете контролювати, які властивості можна ігнорувати за допомогою анотацій або явно вказувати ім'я властивості для класу XStream. Більше того, вам не потрібно реалізовувати інтерфейс, що може бути закритим.
Глибоке копіювання можна зробити лише за згодою кожного класу. Якщо у вас є контроль над ієрархією класів, ви можете реалізувати інтерфейс, що може бути закритий, та застосувати метод Clone. В іншому випадку зробити глибоку копію неможливо зробити безпечно, оскільки об’єкт може також обмінюватися ресурсами, що не належать до даних (наприклад, підключення до бази даних). Як правило, проте глибоке копіювання вважається поганою практикою в середовищі Java, і цього слід уникати за допомогою відповідної практики проектування.
import com.thoughtworks.xstream.XStream;
public class deepCopy {
private static XStream xstream = new XStream();
//serialize with Xstream them deserialize ...
public static Object deepCopy(Object obj){
return xstream.fromXML(xstream.toXML(obj));
}
}
BeanUtils робить дуже гарну роботу з глибоким клонуванням бобів.
BeanUtils.cloneBean(obj);
1)
public static Object deepClone(Object object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
2)
// (1) create a MyPerson object named Al
MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India");
MyPerson al = new MyPerson("Al", "Arun", address);
// (2) make a deep clone of Al
MyPerson neighbor = (MyPerson)deepClone(al);
Тут ваш клас MyPerson і MyAddress повинен реалізовувати інтерфейс, який можна засвоювати
Використання Джексона для серіалізації та десеріалізації об'єкта. Ця реалізація не вимагає від об'єкта реалізації класу Serializable.
<T> T clone(T object, Class<T> clazzType) throws IOException {
final ObjectMapper objMapper = new ObjectMapper();
String jsonStr= objMapper.writeValueAsString(object);
return objMapper.readValue(jsonStr, clazzType);
}