Jak skopiować obiekt w Javie?

794

Rozważ poniższy kod:

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'

Tak, chcę skopiować dumdo dumtwoi zmiana dumbez wpływania na dumtwo. Ale powyższy kod tego nie robi. Kiedy coś dumzmieniam, zachodzi dumtworównież ta sama zmiana .

Kiedy mówię dumtwo = dum, Java kopiuje tylko odniesienie . Czy jest więc jakiś sposób na stworzenie nowej kopii dumi przypisanie jej dumtwo?

Veera
źródło

Odpowiedzi:

611

Utwórz konstruktor kopii:

class DummyBean {
  private String dummy;

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

Każdy obiekt ma również metodę klonowania, której można użyć do skopiowania obiektu, ale nie należy go używać. Zbyt łatwo jest stworzyć klasę i zastosować niewłaściwą metodę klonowania. Jeśli masz zamiar to zrobić, przeczytaj przynajmniej, co Joshua Bloch ma do powiedzenia na ten temat w Effective Java .

egaga
źródło
45
Ale wtedy musiałby zmienić kod na DummyBean dwa = nowy DummyBean (jeden); Dobrze?
Chris K
12
Czy ta metoda skutecznie osiąga to samo, co głęboka kopia?
Matthew Piziak,
124
@MatthewPiziak, dla mnie - nie byłby to głęboki klon, ponieważ wszelkie zagnieżdżone obiekty nadal odwoływałyby się do oryginalnej instancji źródłowej, a nie duplikatu, chyba że każdy obiekt odniesienia (inny niż typ wartości) dostarcza tego samego szablonu konstruktora, jak powyżej.
SliverNinja - MSFT
17
@Timmmm: Tak, będą odnosić się do tego samego ciągu, ale ponieważ jest niezmienny, jest w porządku. To samo dotyczy prymitywów. W przypadku nie-prymitywnych po prostu rekursywnie kopiuj wywołanie contructor. np. jeśli DummyBean odwołuje się do FooBar, wówczas FooBar powinien mieć konstruktora FooBar (FooBar inny), a manekin powinien wywołać this.foobar = nowy FooBar (
inny.foobar
7
@ChristianVielma: Nie, to nie będzie „johndoe”. Jak powiedział Timmmm, sama struna jest niezmienna. Za pomocą jednego, setDummy (..) ustawiasz odwołanie w jednym, aby wskazywało na „johndoe”, ale nie w jednym.
keuleJ
404

Podstawowy: Kopiowanie obiektów w Javie.

Załóżmy obj1, że obiekt- , który zawiera dwa obiekty, zawieraObj1 i zawieraObj2 .
wprowadź opis zdjęcia tutaj

płytkie kopiowanie:
płytkie kopiowanie tworzy nowy instancez tej samej klasy i kopiuje wszystkie pola do nowej instancji i zwraca je. Klasa obiektowa zapewnia clonemetodę i obsługuje płytkie kopiowanie.
wprowadź opis zdjęcia tutaj

Głębokie kopiowanie:
Głęboka kopia występuje, gdy obiekt jest kopiowany wraz z obiektami, do których się odnosi . Poniższe zdjęcie pokazuje obj1po wykonaniu na nim głębokiej kopii. Nie tylko obj1został skopiowany , ale również obiekty w nim zawarte zostały skopiowane. Możemy użyć Java Object Serializationdo wykonania głębokiej kopii. Niestety, takie podejście ma również pewne problemy ( szczegółowe przykłady ).
wprowadź opis zdjęcia tutaj

Możliwe problemy:
clone trudno jest poprawnie wdrożyć.
Lepiej jest używać Defensywnego kopiowania , konstruktorów kopiowania (jako odpowiedź @egaga) lub statycznych metod fabrycznych .

  1. Jeśli masz obiekt, o którym wiesz, że ma clone()metodę publiczną , ale nie znasz typu obiektu w czasie kompilacji, masz problem. Java ma interfejs o nazwie Cloneable. W praktyce powinniśmy wdrożyć ten interfejs, jeśli chcemy stworzyć obiekt Cloneable. Object.clonejest chroniony , dlatego musimy go zastąpić publiczną metodą, aby była dostępna.
  2. Kolejny problem powstaje, gdy staramy głębokie kopiowanie o złożonego obiektu . Załóżmy, że clone()metoda wszystkich zmiennych obiektów składowych również wykonuje głębokie kopiowanie, jest to zbyt ryzykowne z założenia. Musisz kontrolować kod we wszystkich klasach.

Na przykład org.apache.commons.lang.SerializationUtils będzie miał metodę głębokiego klonowania przy użyciu serializacji ( źródło ). Jeśli musimy sklonować Bean, istnieje kilka metod narzędziowych w org.apache.commons.beanutils ( Source ).

  • cloneBean sklonuje komponent bean na podstawie dostępnych modułów pobierających i ustawiających właściwości, nawet jeśli sama klasa bean nie implementuje funkcji Cloneable.
  • copyProperties skopiuje wartości właściwości z komponentu bean źródłowego do komponentu bean docelowego dla wszystkich przypadków, w których nazwy właściwości są takie same.
Chandra Sekhar
źródło
1
Czy możesz wyjaśnić, co zawiera przedmiot w innym?
Freakyuser,
1
@Chandra Sekhar „płytkie kopiowanie tworzy nowe wystąpienie tej samej klasy i kopiuje wszystkie pola do nowego wystąpienia i zwraca je” błędnie jest wspominać wszystkie pola, obiekty bcz nie są kopiowane, tylko odniesienia są kopiowane, co wskazuje na ten sam obiekt, na który wskazywał stary (oryginalny).
JAVA
4
@sunny - opis Chandry jest poprawny. Podobnie jest z twoim opisem tego, co się dzieje; Mówię, że źle rozumiesz znaczenie słowa „skopiuj wszystkie pola”. Pole jest odniesieniem, nie jest to obiekt, do którego się odnosi. „kopiowanie wszystkich pól” oznacza „kopiowanie wszystkich tych odniesień”. Dobrze, że wskazałeś, co to dokładnie oznacza dla każdego, kto ma taką samą błędną interpretację jak ty, stwierdzenia „kopiowanie wszystkich pól”. :)
ToolmakerSteve
2
... jeśli myślimy o pewnym języku OO niższego poziomu, z „wskaźnikami” do obiektów, takie pole zawierałoby adres w pamięci (taki jak „0x70FF1234”), pod którym znajdują się dane obiektu. Ten adres jest „wartością pola”, która jest kopiowana (przypisywana). Masz rację, że wynikiem końcowym jest to, że oba obiekty mają pola, które odnoszą się (wskazują na) ten sam obiekt.
ToolmakerSteve
127

W pakiecie import org.apache.commons.lang.SerializationUtils;jest metoda:

SerializationUtils.clone(Object);

Przykład:

this.myObjectCloned = SerializationUtils.clone(this.object);
Pacheco
źródło
59
Tak długo, jak obiekt implementujeSerializable
Androiderson
2
W tym przypadku sklonowany obiekt nie ma odniesienia do oryginału, jeśli ostatni jest statyczny.
Dante,
8
Biblioteka innej firmy tylko do klonowania obiektu!
Khan
2
@Khan, „biblioteka strony trzeciej tylko” to zupełnie osobna dyskusja! : D
Charles Wood
103

Po prostu wykonaj następujące czynności:

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

i gdziekolwiek chcesz zdobyć inny obiekt, po prostu wykonaj klonowanie. na przykład:

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
Bhasker Tiwari
źródło
1
Testowałeś to? Mógłbym to wykorzystać w moim projekcie i ważne jest, aby być poprawnym.
mglisty
2
@misty Przetestowałem to. Działa doskonale w mojej aplikacji produkcyjnej
Andrii Kovalchuk
Po klonowaniu, modyfikując oryginalny obiekt, modyfikuje również klon.
Sibish
4
To źle, ponieważ nie jest to głęboka kopia, o którą poproszono.
Bluehorn,
1
Ta metoda klonuje wskaźnik wskazujący na obiekt, który można klonować, ale wszystkie właściwości w obu obiektach są takie same, więc w pamięci utworzono nowy obiekt, ale dane w każdym obiekcie są tymi samymi danymi z pamięci
Omar HossamEldin
40

Dlaczego nie ma odpowiedzi na użycie interfejsu API Reflection?

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

To jest naprawdę proste.

EDYCJA: Dołącz obiekt potomny poprzez rekurencję

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
źródło
Wygląda to znacznie lepiej, ale należy wziąć pod uwagę tylko pola końcowe, ponieważ setAccessible (true) może się nie powieść, więc być może trzeba osobno obsłużyć wyjątek IllegalAccessException zgłaszany podczas osobnego wywoływania field.set (klon, field.get (obj)).
Maks.
1
Tak bardzo mi się podobało, ale czy możesz go refaktoryzować, aby używać leków generycznych? prywatny statyczny <T> T cloneObject (T obj) {....}
Adelin
2
Myślę, że to problem, kiedy mamy odniesienie z właściwości do jego rodziców: Class A { B child; } Class B{ A parent; }
nhthai
Nie udaje się nawet w tej sytuacji, trzeba sobie z tym poradzić, zagram jutro. class car { car car = new car(); }
Ján Яabčan
2
Jest to podatne na błędy. Nie jestem pewien, jak poradzi sobie z kolekcjami
ACV
31

Korzystam z biblioteki Google JSON do serializacji, a następnie tworzę nową instancję serializowanego obiektu. Robi głębokie kopiowanie z kilkoma ograniczeniami:

  • nie może być żadnych rekurencyjnych odniesień

  • nie kopiuje tablic różnych typów

  • tablice i listy powinny być wpisane, w przeciwnym razie nie znajdzie klasy do utworzenia

  • może być konieczne enkapsulowanie łańcuchów w klasie, którą sam deklarujesz

Korzystam również z tej klasy do zapisywania preferencji użytkownika, okien i innych elementów, które należy ponownie załadować w czasie wykonywania. Jest bardzo łatwy w użyciu i skuteczny.

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;
}
}
Piotr
źródło
To działa świetnie. Ale uważaj, jeśli spróbujesz sklonować coś takiego jak List <Integer>. Będzie błędny, moje liczby całkowite zamieniły się w Doubles, 100,0. Dużo czasu zajęło mi zrozumienie, dlaczego są tacy. Rozwiązaniem było klonowanie liczb całkowitych jeden po drugim i dodawanie ich do listy w cyklu.
paakjis,
24

Tak, właśnie odwołujesz się do obiektu. Możesz sklonować obiekt, jeśli implementuje Cloneable.

Sprawdź ten artykuł wiki na temat kopiowania obiektów.

Zobacz tutaj: Kopiowanie obiektów

Chrisb
źródło
14

Dodaj Cloneablekod poniżej i do swojej klasy

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

Użyj tego clonedObject = (YourClass) yourClassObject.clone();

Teja Maridu
źródło
12

Tak. Musisz głęboko skopiować swój obiekt.

Bruno Conde
źródło
1
W rzeczywistości nie jest to nawet kopia.
Michael Myers
Jest to prawdopodobnie najmniej pomocna odpowiedź, jaką widziałem na stosie.
Cyryl
12

To też działa. Zakładając model

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

Najpierw dodaj compile 'com.google.code.gson:gson:2.8.1'do swojej aplikacji> gradle & sync. Następnie

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

Możesz wykluczyć użycie pola za pomocą transientsłowa kluczowego po modyfikatorze dostępu.

Uwaga: to zła praktyka. Również nie polecam używać Cloneablelub JavaSerializationJest powolny i uszkodzony. Napisz konstruktor kopiowania dla najlepszej wydajności ref .

Coś jak

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

Statystyki testowe iteracji 90000:
Linia UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);zajmuje 808ms

Linia UserAccount clone = new UserAccount(aO);zajmuje mniej niż 1 ms

Wniosek: użyj gson, jeśli twój szef jest szalony i wolisz szybkość. Jeśli wolisz jakość, użyj drugiego konstruktora kopii.

Możesz także użyć wtyczki generatora kodu konstruktora w Android Studio.

Qamar
źródło
Dlaczego to zasugerowałeś, jeśli jest to zła praktyka?
Parth Mehrotra
Dzięki @ParthMehrotra teraz ulepszone
Qamar,
9

Użyj narzędzia do głębokiego klonowania:

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

Spowoduje to głębokie skopiowanie dowolnego obiektu Java, sprawdź go na https://github.com/kostaskougios/cloning

Cojones
źródło
1
nie działało dla mnie przy użyciu niestandardowej klasy. uzyskiwanie następującego wyjątku: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker
9

Głębokie klonowanie jest odpowiedzią, która wymaga implementacji Cloneableinterfejsu i zastąpienia clone()metody.

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

Nazwiesz to tak DummyBean dumtwo = dum.clone();

Abbas
źródło
2
dummy, a Stringjest niezmienny, nie trzeba go kopiować
Steve Kuo
7

Aby to zrobić, musisz w jakiś sposób sklonować obiekt. Chociaż Java ma mechanizm klonowania, nie używaj go, jeśli nie musisz. Utwórz metodę kopiowania, która wykonuje kopiowanie, a następnie wykonaj następujące czynności:

dumtwo = dum.copy();

Oto kilka porad dotyczących różnych technik tworzenia kopii.

Yishai
źródło
6

Oprócz jawnego kopiowania, innym podejściem jest uczynienie obiektu niezmiennym (brak setlub inne metody mutatora). W ten sposób pytanie nigdy się nie pojawia. Niezmienność staje się trudniejsza w przypadku większych obiektów, ale z drugiej strony jest to, że popycha cię w kierunku podziału na spójne małe obiekty i kompozyty.

Tom Hawtin - tackline
źródło
5

Alternatywa dla konstruktorskiej metody kopiowania egaga . Prawdopodobnie masz już POJO, więc po prostu dodaj inną metodę, copy()która zwraca kopię zainicjowanego obiektu.

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
}

Jeśli masz już DummyBeani chcesz kopię:

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());

Wynik:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Ale oba działają dobrze, to ostatecznie zależy od ciebie ...

Pierre
źródło
3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
Mahdi Abdi
źródło
3

Możesz głęboko kopiować automatycznie za pomocą XStream, z http://x-stream.github.io/ :

XStream to prosta biblioteka do serializacji obiektów do XML iz powrotem.

Dodaj go do swojego projektu (jeśli używasz maven)

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

Następnie

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

Dzięki temu masz kopię bez potrzeby implementowania interfejsu klonowania.

Jaime Hablutzel
źródło
29
Konwersja do / z XML nie wydaje się bardzo ... elegancka. Delikatnie mówiąc!
Timmmm
Spójrz java.beans.XMLEncoderna standardowy interfejs API języka Java, który również serializuje do formatu XML (choć nie jest to dokładnie do celów głębokiego kopiowania).
Jaime Hablutzel,
1
zdajesz sobie sprawę, jak ciężki to jest?
mahieddine,
1
Moim zdaniem droga do dużego obciążenia, ponieważ musisz dodać bibliotekę innej firmy i dokonać serializacji obiektów, co najprawdopodobniej ma ogromny wpływ na wydajność.
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();
   }

  }
}

i w twoim kodzie:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();
Amir Hossein Ghasemi
źródło
2
Nie ma sensu ustawiać „zgłasza wyjątek CloneNotSupportedException” w deklaracji, jeśli spróbujesz złapać wyjątek i nie zostanie on wyrzucony. Więc możesz to po prostu usunąć.
Christian
2

Przekaż obiekt, który chcesz skopiować, i uzyskaj żądany obiekt:

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;

    }

Teraz przeanalizuj objDestżądany obiekt.

Happy Coding!

A-Droid Tech
źródło
1

Możesz spróbować zaimplementować Cloneablei użyć tej clone()metody; Jeśli jednak zastosować metodę klonowania ty powinien - według standardowych - nadpisywanie ZAWSZE Object„s public Object clone()metody.


źródło
1

Jeśli możesz dodać adnotację do pliku źródłowego, można użyć procesora adnotacji lub generatora kodu takiego jak ten .

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Zostanie DummyBeanBuilderswygenerowana klasa , która ma statyczną metodę dummyBeanUpdatertworzenia płytkich kopii, w taki sam sposób, jak robiłbyś to ręcznie.

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();
Lars Bohl
źródło
0

Użyj gsondo powielania obiektu.

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

Przyjmijmy, mam obiektu person.Tak

Person copyPerson = copyObject(person);
tuhin47
źródło