Как мне скопировать объект в Java? - PullRequest
737 голосов
/ 15 мая 2009

Рассмотрим код ниже:

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?

Ответы [ 21 ]

579 голосов
/ 15 мая 2009

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

class DummyBean {
  private String dummy;

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

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

384 голосов
/ 23 марта 2012

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

Давайте предположим, что объект - obj1, который содержит два объекта, containsObj1 и containsObj2 .
enter image description here 1011 *
*

мелкое копирование:
поверхностное копирование создает новый instance того же класса, копирует все поля в новый экземпляр и возвращает его. Класс объекта предоставляет метод clone и поддержку мелкого копирования.
enter image description here 1020 *
*

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

Возможные проблемы:
clone сложно реализовать правильно.
Лучше использовать Защитное копирование , Копировать конструкторы (как ответ @egaga) или Статические фабричные методы .

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

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

  • cloneBean будет клонировать компонент на основе доступных методов получения и установки свойств, даже если сам класс компонента не реализует Cloneable.
  • copyProperties Скопирует значения свойств из исходного объекта в целевой компонент для всех случаев, когда имена свойств совпадают.
110 голосов
/ 02 мая 2013

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

SerializationUtils.clone(Object);

Пример:

this.myObjectCloned = SerializationUtils.clone(this.object);
97 голосов
/ 17 мая 2010

Просто следуйте инструкциям ниже:

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
36 голосов
/ 16 августа 2014

Почему нет ответа на использование 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;
        }
    }

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

РЕДАКТИРОВАТЬ: включить дочерний объект с помощью рекурсии

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;
        }
    }
26 голосов
/ 10 июля 2013

Я использую библиотеку 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 голосов
/ 15 мая 2009

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

Прочтите эту статью в вики о копировании объектов.

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

13 голосов
/ 15 мая 2009

Да. Вам необходимо Deep Copy вашего объекта.

12 голосов
/ 31 июля 2013

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

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

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

10 голосов
/ 26 января 2017

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

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 Это медленно и сломано. Напишите конструктор копирования для лучшей производительности ref .

Что-то вроде

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 итераций:
Линия UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class); занимает 808 мс

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

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

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

...