Как мне скопировать объект в Java?

693

Рассмотрим следующий код:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

Итак, я хочу скопировать dum на dumtwo и изменить dum не затрагивая dumtwo. Но вышеприведенный код не делает этого. Когда я что-то меняю в dum, то такое же изменение происходит и в dumtwo.

Я думаю, когда я говорю dumtwo = dum, Java копирует только ссылку. Итак, есть ли способ создать новую копию dum и присвоить ей dumtwo?

Теги:
object
copy
clone

21 ответ

509
Лучший ответ

Создайте конструктор копирования:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

Каждый объект имеет также метод клонирования, который можно использовать для копирования объекта, но не использовать его. Слишком легко создать класс и сделать неправильный метод клонирования. Если вы собираетесь это сделать, прочитайте, по крайней мере, то, что Джошуа Блох должен сказать об этом в Эффективная Java.

  • 40
    Но тогда ему придется изменить свой код на DummyBean two = new DummyBean (one); Правильно?
  • 4
    @ Крис Камински Правильно
Показать ещё 15 комментариев
358

Основные: Копирование объектов на Java.

Предположим, что объект- obj1, содержащий два объекта, содержит Obj1 и , содержащий Obj2.
Изображение 1418

мелкое копирование:
мелкое копирование создает новый instance того же класса и копирует все поля в новый экземпляр и возвращает его. Класс объекта предоставляет метод clone и обеспечивает поддержку мелкого копирования.
Изображение 1419

Глубокое копирование:
Глубокая копия возникает, когда объект копируется вместе с объектами, к которым он относится. Ниже изображения отображается obj1 после того, как на нем была выполнена глубокая копия. Скопировано не только obj1, но и объекты, содержащиеся в нем, были скопированы. Мы можем использовать Java Object Serialization, чтобы сделать глубокую копию. К сожалению, этот подход также имеет некоторые проблемы (подробные примеры).
Изображение 1420

Возможные проблемы:
clone сложно реализовать правильно.
Лучше использовать Оборонительное копирование, конструкторы копирования (как @egaga reply) или статические методы factory.

  • Если у вас есть объект, который, как вы знаете, имеет общедоступный метод clone(), но вы не знаете тип объекта во время компиляции, тогда у вас есть проблема. Java имеет интерфейс под названием Cloneable. На практике мы должны реализовать этот интерфейс, если хотим создать объект Cloneable. Object.clone защищен, поэтому мы должны переопределить его общедоступным методом, чтобы он был доступен.
  • Другая проблема возникает, когда мы пытаемся выполнить глубокое копирование сложного объекта. Предположим, что метод clone() всех переменных-членов-членов также делает глубокую копию, это слишком рискованно из предположения. Вы должны контролировать код во всех классах.

Например org.apache.commons.lang.SerializationUtils будет иметь метод для Deep clone с использованием сериализации (Source). Если нам нужно клонировать Bean, тогда в org.apache.commons.beanutils есть несколько методов утилиты (Источник).

  • cloneBean будет клонировать Bean на основе доступных свойств getters и seters, даже если сам класс Bean не реализует Cloneable.
  • copyProperties скопирует значения свойств из источника Bean в пункт назначения Bean для всех случаев, когда имена свойств совпадают.
  • 1
    Можете ли вы объяснить, что находится внутри другого объекта?
  • 1
    @Chandra Sekhar "поверхностное копирование создает новый экземпляр того же класса и копирует все поля в новый экземпляр и возвращает его" неправильно упоминать все поля, объекты bcz не копируются, копируются только ссылки, которые указывают на тот же объект, на который указывал старый (оригинал).
Показать ещё 2 комментария
98

В пакете import org.apache.commons.lang.SerializationUtils; есть метод:

SerializationUtils.clone(Object);

Пример:

this.myObjectCloned = SerializationUtils.clone(this.object);
  • 43
    Пока объект реализует Serializable
  • 1
    В этом случае клонированный объект не имеет ссылки на оригинал, если последний является статическим.
Показать ещё 2 комментария
93

Просто следуйте ниже:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

и везде, где вы хотите получить другой объект, просто выполните клонирование. например:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object
  • 1
    Вы проверяли это? Я мог бы использовать это для своего проекта, и важно быть правильным.
  • 2
    @misty Я проверял это. Отлично работает на моем производственном приложении
Показать ещё 5 комментариев
35

Почему нет ответа на использование Reflection API?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

Это действительно просто.

EDIT: включить дочерний объект через рекурсию

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }
  • 0
    Это выглядит намного лучше, но вам нужно только учитывать конечные поля, так как setAccessible (true) может потерпеть неудачу, поэтому, возможно, вам нужно отдельно обработать исключение IllegalAccessException, генерируемое при вызове field.set (clone, field.get (obj)) по отдельности.
  • 1
    Мне так понравилось, но можете ли вы сделать рефакторинг для использования дженериков? приватный статический <T> T cloneObject (T obj) {....}
Показать ещё 8 комментариев
23

Я использую библиотеку Google JSON для сериализации, а затем создаю новый экземпляр сериализованного объекта. Он делает глубокую копию с несколькими ограничениями:

  • не может быть рекурсивных ссылок

  • он не будет копировать массивы разрозненных типов

  • массивы и списки должны быть напечатаны или он не найдет класс для создания экземпляра

  • вам может потребоваться инкапсуляция строк в класс, который вы объявляете сами

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

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}
22

Да, вы просто делаете ссылку на объект. Вы можете клонировать объект, если он реализует Cloneable.

Ознакомьтесь с этой статьей wiki о копировании объектов.

См. Здесь: Копирование объектов

12

Добавьте Cloneable и ниже код в свой класс

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

Используйте этот clonedObject = (YourClass) yourClassObject.clone();

12

Да. Вам нужно Deep Copy ваш объект.

  • 1
    Как есть, это даже не копия вообще.
9

Вот достойное объяснение clone(), если вам это нужно...

Здесь: clone (метод Java)

8

Это тоже работает. Предполагаемая модель

class UserAccount{
   public int id;
   public String name;
}

Сначала добавьте compile 'com.google.code.gson:gson:2.8.1' в ваше приложение> gradle & sync. затем

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

Вы можете исключить использование поля с помощью transient ключевого слова после модификатора доступа.

Примечание. Это плохая практика. Также не рекомендуется использовать Cloneable или JavaSerialization Он медленный и сломанный. Написать конструктор копирования для лучшей производительности исх.

Что-то вроде

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

Испытательная статистика итерации 90000:
Line UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class); занимает 808 мс

Line UserAccount clone = new UserAccount(aO); занимает менее 1 мс

Вывод: используйте gson, если ваш босс сумасшедший, и вы предпочитаете скорость. Используйте второй конструктор копий, если вы предпочитаете качество.

Вы также можете использовать плагин генерации кода конструктора кода в Android Studio.

  • 0
    Почему вы предложили это, если это плохая практика?
  • 0
    Спасибо @ParthMehrotra теперь улучшилось
7

Глубокое клонирование - это ваш ответ, который требует реализации интерфейса Cloneable и переопределения метода clone().

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types, 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

Вы назовете это так DummyBean dumtwo = dum.clone();

  • 2
    dummy , String , является неизменной, вам не нужно копировать ее
7

Чтобы сделать это, вы должны каким-то образом клонировать объект. Хотя Java имеет механизм клонирования, не используйте его, если вам это не нужно. Создайте метод копирования, который выполняет копирование для вас, а затем выполните:

dumtwo = dum.copy();

Здесь есть еще несколько советов по различным методам выполнения копии.

6

Используйте утилиту глубокого клонирования:

SomeObjectType copy = new Cloner().deepClone(someObject);

Это будет глубоко скопировать любой Java-объект, проверьте его на https://github.com/kostaskougios/cloning

  • 1
    не работал для меня, используя пользовательский класс. получить следующее исключение: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
5

Помимо явного копирования, другой подход заключается в том, чтобы сделать объект неизменным (no set или другие методы мутатора). Таким образом, вопрос никогда не возникает. Неизменность становится более сложной с более крупными объектами, но другая сторона заключается в том, что она толкает вас в направлении расщепления на когерентные мелкие объекты и композиты.

3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
2

Передайте объект, который вы хотите скопировать, и получите объект, который вы хотите,

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

Теперь проанализируйте объект objDest с объектом, находящимся в поиске.

Счастливое кодирование

1

Вы можете выполнить глубокую копию с помощью XStream, http://x-stream.github.io/:

XStream - простая библиотека для сериализации объектов в XML и обратно еще раз.

Добавьте его в свой проект (при использовании maven)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

Тогда

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

При этом у вас есть копия без необходимости реализовывать какой-либо интерфейс клонирования.

  • 28
    Преобразование в / из XML не кажется очень ... элегантным. Мягко говоря!
  • 0
    Посмотрите на java.beans.XMLEncoder для стандартного Java API, который также сериализуется в XML (хотя и не для целей глубокого копирования).
Показать ещё 2 комментария
1

Вы можете попробовать реализовать Cloneable и использовать метод clone(); однако, если вы используете метод clone, вы должны - по стандарту - ВСЕГДА переопределять метод Object public Object clone().

0
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

и в вашем коде:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();
  • 1
    Нет никакого смысла в наборе «throws CloneNotSupportedException» в объявлении, если вы попытаетесь перехватить исключение и его не выбросить. Таким образом, вы можете просто удалить его.
0

Если вы можете добавить аннотацию к исходному файлу, то обработчик аннотации или генератор кода, например этот может быть б.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Будет создан класс DummyBeanBuilders, у которого есть статический метод dummyBeanUpdater для создания неглубоких копий, так же, как вы делали бы это вручную.

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();

Ещё вопросы

Сообщество Overcoder
Наверх
Меню