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

794

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

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?

Веера
источник

Ответы:

611

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

class DummyBean {
  private String dummy;

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

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

egaga
источник
45
Но тогда ему придется изменить свой код на DummyBean two = new DummyBean (one); Правильно?
Крис К
12
Эффективно ли этот метод выполняет то же самое, что и глубокая копия?
Мэтью Пизиак
124
@MatthewPiziak, для меня - это не будет глубоким клоном, поскольку любые вложенные объекты будут по-прежнему ссылаться на исходный экземпляр источника, а не на дубликаты, если каждый объект ссылки (не тип значения) не предоставляет тот же шаблон конструктора, что и выше.
SliverNinja - MSFT
17
@Timmmm: Да, они будут ссылаться на одну и ту же строку, но поскольку она неизменна, это нормально. То же самое касается примитивов. Для не примитивов вы бы просто сделали рекурсивный вызов вызова contructor. Например, если DummyBean ссылается на FooBar, то FooBar должен иметь конструктор FooBar (FooBar другой), а dummy должен вызывать this.foobar = new FooBar (another.foobar)
egaga
7
@ChristianVielma: нет, это не будет "johndoe". Как сказал Тимммм, сама строка неизменна. С помощью setDummy (..) вы устанавливаете ссылку в одном так, чтобы она указывала на "johndoe", но не в одном.
keuleJ
404

Базовый: Копирование объектов в Java.

Давайте предположим obj1, что объект - содержит два объекта, содержащихся в Obsj1 и в составе В2 .
введите описание изображения здесь

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

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

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

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

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

  • cloneBean Будет клонировать компонент на основе доступных методов получения и установки свойств, даже если сам класс компонента не реализует Cloneable.
  • copyProperties Скопирует значения свойств из исходного объекта в целевой для всех случаев, когда имена свойств совпадают.
Чандра Сехар
источник
1
Можете ли вы объяснить, что находится внутри другого объекта?
Freakyuser
1
@Chandra Sekhar "поверхностное копирование создает новый экземпляр того же класса и копирует все поля в новый экземпляр и возвращает его" неправильно упоминать все поля, объекты bcz не копируются, копируются только ссылки, которые указывают на тот же объект, на который указывал старый (оригинал).
JAVA
4
@sunny - описание Чандры верно. Как и ваше описание того, что происходит; Я говорю, что вы неправильно понимаете значение «копирует все поля». Поле является ссылкой, а не объектом, на который ссылаются. «Копирование всех полей» означает «копирование всех этих ссылок». Хорошо, что вы указали, что именно это означает для всех, у кого такое же неверное толкование, как у вас, утверждения «копирование всех полей». :)
ToolmakerSteve
2
... если мы думаем с точки зрения некоторого языка ОО нижнего уровня с «указателями» на объекты, такое поле будет содержать адрес в памяти (такой как «0x70FF1234»), по которому находятся данные объекта. Этот адрес является «значением поля», которое копируется (присваивается). Вы правы в том, что конечным результатом является то, что оба объекта имеют поля, которые ссылаются (указывают на) на один и тот же объект.
ToolmakerSteve
127

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

SerializationUtils.clone(Object);

Пример:

this.myObjectCloned = SerializationUtils.clone(this.object);
Пачеко
источник
59
Пока объект реализуетSerializable
Androiderson
2
В этом случае клонированный объект не имеет ссылки на оригинал, если последний является статическим.
Данте
8
Сторонняя библиотека просто для клонирования объекта!
Хан,
2
@ Хан, «сторонняя библиотека только для» - это совершенно отдельная дискуссия! : D
Чарльз Вуд
103

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

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 Я проверял это. Прекрасно работает на моем производственном приложении
Андрей Ковальчук
После клонирования, когда вы изменяете исходный объект, он также модифицирует клон.
сибиш
4
Это неправильно, потому что это не глубокая копия, о которой просили.
Bluehorn
1
Этот метод клонирования указатель , который точки для Cloneable объекта, но все свойства внутри обоих объектов одинаковы, поэтому есть новый объект , созданный в памяти, но данные внутри каждого объекта одни и те же данные из памяти
Омар HossamEldin
40

Почему нет ответа на использование 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;
        }
    }
WillingLearner
источник
Это выглядит намного лучше, но вам нужно только рассмотреть конечные поля, так как setAccessible (true) может потерпеть неудачу, поэтому, возможно, вам нужно отдельно обработать исключение IllegalAccessException, генерируемое при вызове field.set (clone, field.get (obj)) по отдельности.
Макс
1
Мне так понравилось, но можете ли вы изменить его, чтобы использовать дженерики? приватная статическая <T> T cloneObject (T obj) {....}
Аделин
2
Я думаю, что это проблема, когда у нас есть ссылки из свойств на него родителей: Class A { B child; } Class B{ A parent; }
nhthai
Это не удается даже в этой ситуации, должны быть обработаны, я буду играть с ним завтра. class car { car car = new car(); }
Ян Яабчан,
2
Это подвержено ошибкам. Не уверен, как это будет обращаться с коллекциями
ACV
31

Я использую библиотеку 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;
}
}
Питер
источник
Это прекрасно работает. Но будьте осторожны, если вы попытаетесь клонировать что-то вроде List <Integer>. Это будет глючить, мои целые числа превратились в удвоения, 100.0. Мне потребовалось много времени, чтобы понять, почему они такие. Решение состояло в том, чтобы клонировать целые числа их один за другим и добавлять в список в цикле.
Paakjis
14

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

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

Использовать этот clonedObject = (YourClass) yourClassObject.clone();

Теджа Мариду
источник
12

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

Бруно Конде
источник
1
Как есть, это даже не копия вообще.
Майкл Майерс
Это, вероятно, наименее полезный ответ, который я видел в stackoverflow.
Кирилл
12

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

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.

Камар
источник
Почему вы предложили это, если это плохая практика?
Парт Мехротра
Благодаря @ParthMehrotra настоящее время улучшилось
Камар
9

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

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

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

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

Глубокое клонирование - это ваш ответ, который требует реализации 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 like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

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

аббас
источник
2
dummy, A String, неизменен, вам не нужно копировать его
Стив Его
7

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

dumtwo = dum.copy();

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

Ишай
источник
6

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

Том Хотин - Tackline
источник
5

Альтернатива конструктору метода egaga копирования. Возможно, у вас уже есть POJO, поэтому просто добавьте еще один метод, copy()который возвращает копию инициализированного объекта.

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

Если у вас уже есть DummyBeanи вам нужна копия:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

Вывод:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Но оба хорошо работают, это в конечном итоге зависит от вас ...

пьер
источник
3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
Махди Абди
источник
3

Вы можете глубоко копировать автоматически с помощью 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));

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

Хайме Хаблутцель
источник
29
Преобразование в / из XML не кажется очень ... элегантным. Мягко говоря!
Тимммм
Обратите внимание на java.beans.XMLEncoderстандартный Java API, который также сериализуется в XML (хотя и не для целей глубокого копирования).
Хайме Хаблутцель
1
ты понимаешь, насколько это тяжело?
Mahieddine
1
На мой взгляд, это слишком много, так как вам нужно добавить стороннюю библиотеку и выполнить сериализацию объектов, что, скорее всего, окажет огромное влияние на производительность.
NiThDi
2
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();
Амир Хоссейн Гасеми
источник
2
Нет никакого смысла в наборе «throws CloneNotSupportedException» в объявлении, если вы попытаетесь перехватить исключение и не будет выброшено. Итак, вы можете просто удалить его.
Кристиан
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нужный объект.

Удачного кодирования!

A-Droid Tech
источник
1

Вы можете попытаться реализовать Cloneableи использовать clone()метод; Однако, если вы используете метод клонирования Вам необходимы - по стандартному - всегда отменяет Object«s public Object clone()метода.


источник
1

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

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();
Ларс Бол
источник
0

Используйте gsonдля дублирования объекта.

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

Предположим , у меня есть объект person.so

Person copyPerson = copyObject(person);
tuhin47
источник