Як скопіювати об’єкт на 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  
  }
}

Кожен об’єкт також має метод клонування, який можна використовувати для копіювання об'єкта, але не використовувати його. Створити клас і зробити метод неправильного клонування дуже легко. Якщо ви збираєтеся це зробити, прочитайте принаймні те, що Джошуа Блох має про це сказати в « Ефективній Java» .


45
Але тоді йому доведеться змінити код на DummyBean два = новий DummyBean (один); Правильно?
Кріс К

12
Чи ефективно цей метод виконує те саме, що і глибока копія?
Матвій Пізіак

124
@MatthewPiziak, для мене - це не був би глибоким клоном, оскільки будь-які вкладені об'єкти все ще посилаються на оригінальний екземпляр джерела, а не на дублікат, якщо кожен посилання (нецільовий тип) об'єкт надає той же шаблон конструктора, що і вище.
SliverNinja - MSFT

17
@Timmmm: Так, вони посилаються на ту саму String, але оскільки вона незмінна, це нормально. Те саме стосується примітивів. Для непримітивів ви просто виконуватимете виклик кондуктора рекурсивно. наприклад , якщо DummyBean посилання FooBar то FooBar повинен мати забудовник FooBar (FooBar інший), і манекен повинен викликати this.foobar = новий FooBar (another.foobar)
egaga

7
@ChristianVielma: Ні, це не буде "джондо". Як сказав Тімммм, сама рядок незмінна. З одним, setDummy (..) ви встановлюєте посилання в одному, щоб вказувати на "johndoe", але не на одне в одному.
keuleJ

404

Основні: Копіювання об'єктів на Java.

Припустимо, об'єкт- obj1, який містить два об'єкти, містивObj1 та містив Obj2 .
введіть тут опис зображення

неглибоке копіювання:
дрібне копіювання створює новий instanceтого ж класу і копіює всі поля в новий екземпляр і повертає його. Клас об'єктів забезпечує cloneметод і забезпечує підтримку дрібного копіювання.
введіть тут опис зображення

Глибоке копіювання:
глибока копія виникає, коли об'єкт копіюється разом з об'єктами, на які він посилається . Знизу зображення відображається obj1після того, як на ньому виконана глибока копія. Не тільки obj1було скопійовано , але й об'єкти, що містяться в ньому, скопійовано. Ми можемо використовувати Java Object Serializationдля створення глибокої копії. На жаль, у цього підходу є і деякі проблеми ( детальні приклади ).
введіть тут опис зображення

Можливі проблеми:
clone складно виконати правильно.
Краще використовувати захисне копіювання , конструктори копій (як @egaga відповідь) або статичні заводські методи .

  1. Якщо у вас є об'єкт, який, на вашу думку, має публічний clone()метод, але ви не знаєте тип об'єкта під час компіляції, тоді у вас є проблеми. У Java є інтерфейс під назвою Cloneable. На практиці нам слід реалізувати цей інтерфейс, якщо ми хочемо зробити об’єкт Cloneable. Object.cloneбуде захищена , тому ми повинні перевизначити його за допомогою відкритого методу для того , щоб бути доступним.
  2. Інша проблема виникає , коли ми намагаємося глибоке копіювання у вигляді складного об'єкта . Припустимо, що clone()метод усіх змінних об'єктів-членів також робить глибоку копію, це занадто ризикує припущення. Ви повинні керувати кодом у всіх класах.

Наприклад, org.apache.commons.lang.SerializationUtils матиме метод для глибокого клонування з використанням серіалізації ( Джерело ). Якщо нам потрібно клонувати Біна, то в org.apache.commons.beanutils є кілька корисних методів ( Джерело ).

  • cloneBean буде клонувати квасоля на основі доступних одержувачів властивостей та сеттерів, навіть якщо сам клас bean не реалізує Cloneable.
  • copyProperties Копіюватиме значення властивостей з початкового джерела в бій призначення для всіх випадків, коли назви властивостей однакові.

1
Чи можете ви пояснити, що об'єкт міститься в іншому?
Freakyuser

1
@Chandra Sekhar "дрібне копіювання створює новий екземпляр того ж класу і копіює всі поля в новий екземпляр і повертає його", що неправильно згадувати всі поля, bcz-об'єкти не копіюються, тільки копії посилань, на які вказується той самий предмет, на який вказував старий (оригінал).
JAVA

4
@sunny - опис Чандри правильний. Так само і ваш опис того, що відбувається; Я кажу, що ви неправильно розумієте значення "копіює всі поля". Поле є посиланням, воно не є об'єктом, на який посилаються. "копіювання всіх полів" означає "копіювання всіх цих посилань". Добре, що ви вказали, що саме це означає для тих, хто має таке ж неправильне тлумачення, як і ви, твердження "копіювання всіх полів". :)
ToolmakerSteve

2
... якщо ми думаємо з точки зору деякої мови OO нижчого рівня, з "покажчиками" на об'єкти, таке поле містило б адресу в пам'яті (наприклад, "0x70FF1234"), в якій знайдені дані об'єкта. Ця адреса - це "значення поля", яке копіюється (присвоюється). Ви впевнені, що кінцевим результатом є те, що обидва об'єкти мають поля, які посилаються на (вказують на) один і той же об’єкт.
ToolmakerSteve

127

У пакеті import org.apache.commons.lang.SerializationUtils;є метод:

SerializationUtils.clone(Object);

Приклад:

this.myObjectCloned = SerializationUtils.clone(this.object);

59
Поки об’єкт реалізовуєтьсяSerializable
Androiderson

2
У цьому випадку клонований об’єкт не має посилання на оригінал, якщо останній є статичним.
Данте

8
Стороння бібліотека просто для клонування об’єкта!
Хан

2
@Khan, "стороння бібліотека тільки для" - це абсолютно окрема дискусія! : 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
Це неправильно, оскільки це не була глибока копія, яку вимагали.
Блюхорн

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;
        }
    }

Це дійсно просто.

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;
        }
    }

Це виглядає набагато краще, але вам потрібно розглянути лише остаточні поля, оскільки 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

Я використовую бібліотеку JSON Google, щоб серіалізувати її, а потім створити новий екземпляр серіалізованого об'єкта. Це копія із глибоким обмеженням:

  • не може бути ніяких рекурсивних посилань

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

  • масиви та списки повинні бути набрані, або він не знайде клас для інстанції

  • Вам може знадобитися інкапсулювати рядки в класі, який ви заявляєте про себе

Я також використовую цей клас для збереження налаштувань користувачів, Windows та чого не потрібно перезавантажувати під час виконання. Він дуже простий у використанні та ефективний.

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>. Це буде баггі, мої Integers перетворилися на парні, 100.0. Мені знадобилося багато часу, щоб зрозуміти, чому вони такі. Рішення полягало в тому, щоб клонувати їх цілими по черзі та додавати до списку за циклом.
paakjis


14

Додайте Cloneableі нижче код у свій клас

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

Використовуй це clonedObject = (YourClass) yourClassObject.clone();



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.


Чому ви запропонували це, якщо це погана практика?
Parth Mehrotra

Завдяки @ParthMehrotra даний час покращився
Камар


9

Використовуйте утиліту глибокого клонування:

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

Це дозволить глибоко скопіювати будь-який об’єкт Java, перевірте це на веб-сторінці https://github.com/kostaskougios/cloning


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інших або інших мутаторних методів). Таким чином питання ніколи не виникає. Незмінність стає складнішою для великих об'єктів, але інша сторона цього полягає в тому, що вона підштовхує вас у напрямку до розщеплення на цілісні невеликі предмети та композити.


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

Ви можете автоматично копіювати за допомогою 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 не видається дуже елегантним. М’яко кажучи!
Timmmm

Погляньте на 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
Немає сенсу встановлювати "кидає 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потрібний об’єкт.

Щасливе кодування!


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. Отже

Person copyPerson = copyObject(person);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.