Java: рекомендуемое решение для глубокого клонирования / копирования экземпляра
Мне интересно, есть ли рекомендуемый способ сделать глубокий клон / копию экземпляра в Java.
Я имею в виду 3 решения, но я могу пропустить некоторые, и я хотел бы узнать ваше мнение
Отредактируйте: включите Bohzo propositon и уточните вопрос: это больше о глубоком клонировании, чем мелком клонировании.
Сделай это сам:
закодируйте свойства клона вручную после свойств и убедитесь, что изменяемые экземпляры тоже клонируются.
про:
- контроль того, что будет выполнено
- быстрое исполнение
минусы:
- утомительно писать и поддерживать
- склонна к ошибкам (ошибка копирования / вставки, отсутствующее свойство, переназначенное изменяемое свойство)
Используйте отражение:
С помощью ваших собственных инструментов отражения или с помощью внешнего помощника (например, Jakarta Common-Beans) легко написать общий метод копирования, который будет выполнять работу в одну строку.
про:
- легко написать
- без обслуживания
минусы:
- меньше контроля над тем, что происходит
- ошибка подвержена изменчивому объекту, если инструмент отражения не клонирует подчиненные объекты также
- медленное выполнение
Используйте клон рамки:
Используйте фреймворк, который сделает это за вас, например:
Commons-lang SerializationUtils
Библиотека глубокого клонирования Java
бульдозер
Kryo
про:
- так же, как отражение
- больше контроля над тем, что именно будет клонировано.
минусы:
- каждый изменяемый экземпляр полностью клонируется, даже в конце иерархии
- может быть очень медленным для выполнения
Используйте инструментарий байт-кода для записи клона во время выполнения
javassit, BCEL или cglib могут быть использованы для генерации выделенного клонера так же быстро, как написана одна рука. Кто-то знает библиотеку, использующую один из этих инструментов для этой цели?
Что я здесь упустил?
Какой из них вы бы порекомендовали?
Благодарю.
11 ответов
Для глубокого клонирования (клонирует всю иерархию объектов):
commons-lang SerializationUtils - используя сериализацию - если все классы находятся под вашим контролем, и вы можете принудительно реализовать
Serializable
,Java Deep Cloning Library - использование отражения - в случаях, когда классы или объекты, которые вы хотите клонировать, находятся вне вашего контроля (сторонняя библиотека), и вы не можете заставить их реализовать
Serializable
или в случаях, когда вы не хотите реализовыватьSerializable
,
Для мелкого клонирования (клонирует только свойства первого уровня):
commons-beanutils BeanUtils - в большинстве случаев.
Spring BeanUtils - если вы уже используете spring и, следовательно, имеете эту утилиту на пути к классам.
Я намеренно опустил опцию "сделай сам" - вышеприведенные API обеспечивают хороший контроль над тем, что клонировать, а что нет (например, используя transient
, или же String[] ignoreProperties
), поэтому изобретать велосипед не является предпочтительным.
В книге Джошуа Блоха есть целая глава, озаглавленная "Пункт 10: разумно переопределить клон", в которой он объясняет, почему переопределение клона по большей части является плохой идеей, потому что спецификация Java для него создает много проблем.
Он предлагает несколько альтернатив:
Используйте шаблон фабрики вместо конструктора:
public static Yum newInstance(Yum yum);
Используйте конструктор копирования:
public Yum(Yum yum);
Все классы коллекции в Java поддерживают конструктор копирования (например, new ArrayList(l);)
Начиная с версии 2.07 Kryo поддерживает мелкое / глубокое клонирование:
Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);
Kryo быстр, на их странице вы можете найти список компаний, которые используют его в производстве.
Используйте XStream toXML/fromXML в памяти. Чрезвычайно быстро и уже давно и становится сильным. Объекты не должны быть сериализуемыми, и вы не должны использовать отражение (хотя XStream делает). XStream может различать переменные, которые указывают на один и тот же объект, и не случайно делает две полные копии экземпляра. Много таких деталей вырабатывалось годами. Я использовал это в течение многих лет, и это начало. Это так же просто, как вы можете себе представить.
new XStream().toXML(myObj)
или же
new XStream().fromXML(myXML)
Клонировать,
new XStream().fromXML(new XStream().toXML(myObj))
Более кратко:
XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Для сложных объектов и когда производительность невелика, я использую gson для сериализации объекта в текст json, а затем десериализацию текста для получения нового объекта.
GSON, который на основе отражения будет работать в большинстве случаев, за исключением того, что transient
поля не будут скопированы и объекты с круговой ссылкой с причиной StackruError
,
public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
Gson gson = new GsonBuilder().create();
String text = gson.toJson(AnObject);
ObjectType newObject = gson.fromJson(text, ClassInfo);
return newObject;
}
public static void main(String[] args)
{
MyObject anObject ...
MyObject copyObject = Copy(o, MyObject.class);
}
Зависит.
Для скорости используйте DIY. Для пуленепробиваемости используйте отражение.
Кстати, сериализация отличается от refl, так как некоторые объекты могут предоставлять переопределенные методы сериализации (readObject/writeObject), и они могут содержать ошибки
Я бы предложил переопределить Object.clone(), сначала вызвать super.clone(), а затем вызвать ref = ref.clone() для всех ссылок, которые вы хотите глубоко скопировать. Это более или менее Сделай сам подход, но нужно немного меньше кодирования.
Для глубокого клонирования реализуйте Serializable для каждого класса, который вы хотите клонировать следующим образом
public static class Obj implements Serializable {
public int a, b;
public Obj(int a, int b) {
this.a = a;
this.b = b;
}
}
А затем используйте эту функцию:
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;
}
}
как это: Obj newObject = (Obj)deepClone(oldObject);
Я бы порекомендовал способ DIY, который в сочетании с хорошими методами hashCode() и equals() должен легко проверяться в модульном тесте.
Взгляните на библиотеку сахарных кубиков-клонеров . Это очень быстро и хорошо документировано. Это также позволяет разработчику очень гибко настраивать процесс клонирования на уровне классов, полей и объектов.
Просто используйте MicroStream Object Copier.
ObjectCopier objectCopier = ObjectCopier.New();
Customer customer = root.getCustomer(id);
Customer customerCopy = objectCopier.copy(customer);
Эта утилита обеспечивает полную глубокую копию любого графа объектов в Java. Будьте осторожны с циклическими ссылками. Вы можете легко сделать копию всего графа памяти.
https://docs.microstream.one/manual/storage/storing-data/deep-copy.html