Wie kopiere ich ein Objekt in Java?

794

Betrachten Sie den folgenden Code:

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'

Also, ich möchte , dass die kopieren dumzu dumtwound ändern , dumohne die Auswirkungen auf dumtwo. Aber der obige Code macht das nicht. Wenn ich etwas dumändere, geschieht die gleiche Änderung dumtwoauch in.

Ich denke, wenn ich sage dumtwo = dum, kopiert Java nur die Referenz . Gibt es eine Möglichkeit, eine neue Kopie zu erstellen dumund dieser zuzuweisen dumtwo?

Veera
quelle

Antworten:

611

Erstellen Sie einen Kopierkonstruktor:

class DummyBean {
  private String dummy;

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

Jedes Objekt verfügt auch über eine Klonmethode, mit der das Objekt kopiert werden kann, die jedoch nicht verwendet wird. Es ist viel zu einfach, eine Klasse zu erstellen und eine falsche Klonmethode auszuführen. Wenn Sie das tun wollen, lesen Sie zumindest, was Joshua Bloch dazu in Effective Java zu sagen hat .

egaga
quelle
45
Aber dann müsste er seinen Code in DummyBean two = new DummyBean (one) ändern; Recht?
Chris K
12
Erreicht diese Methode effektiv dasselbe wie eine tiefe Kopie?
Matthew Piziak
124
@MatthewPiziak, für mich - dies wäre kein tiefer Klon, da verschachtelte Objekte immer noch auf die ursprüngliche Quellinstanz verweisen würden, kein Duplikat, es sei denn, jedes Referenzobjekt (Nicht-Wert-Typ) liefert dieselbe Konstruktorvorlage wie oben.
SliverNinja - MSFT
17
@Timmmm: Ja, sie werden auf denselben String verweisen, aber da er unveränderlich ist, ist er in Ordnung. Gleiches gilt für Primitive. Bei Nicht-Grundelementen würden Sie den Konstruktoraufruf nur rekursiv kopieren. zB Wenn DummyBean auf FooBar verweist, sollte FooBar einen Konstruktor FooBar (FooBar ein anderer) haben, und Dummy sollte this.foobar = new FooBar (ein
anderer.foobar
7
@ChristianVielma: Nein, es wird nicht "Johndoe" sein. Wie Timmmm sagte, ist die Saite selbst unveränderlich. Mit one, setDummy (..) setzen Sie die Referenz in one, um auf "johndoe" zu zeigen, aber nicht auf one in one.
KeuleJ
404

Grundlegend: Objektkopieren in Java.

Lassen Sie uns eine objekt- Angenommen obj1, dass enthält zwei Objekte, containedObj1 und containedObj2 .
Geben Sie hier die Bildbeschreibung ein

Flaches Kopieren: Beim
flachen Kopieren wird ein neues instanceFeld derselben Klasse erstellt, alle Felder werden in die neue Instanz kopiert und zurückgegeben. Die Objektklasse bietet eine cloneMethode und unterstützt das flache Kopieren.
Geben Sie hier die Bildbeschreibung ein

Tiefes Kopieren:
Eine tiefe Kopie erfolgt, wenn ein Objekt zusammen mit den Objekten kopiert wird, auf die es sich bezieht . Das folgende Bild zeigt, obj1nachdem eine tiefe Kopie darauf durchgeführt wurde. Es wurde nicht nur obj1kopiert , sondern auch die darin enthaltenen Objekte wurden kopiert. Wir können verwenden Java Object Serialization, um eine tiefe Kopie zu erstellen. Leider weist dieser Ansatz auch einige Probleme auf ( detaillierte Beispiele ).
Geben Sie hier die Bildbeschreibung ein

Mögliche Probleme: Die
clone korrekte Implementierung ist schwierig.
Es ist besser, defensives Kopieren , Kopierkonstruktoren (als @ egaga-Antwort) oder statische Factory-Methoden zu verwenden .

  1. Wenn Sie ein Objekt haben, von dem Sie wissen, dass es eine öffentliche clone()Methode hat, aber den Typ des Objekts zur Kompilierungszeit nicht kennen, liegt ein Problem vor. Java hat eine Schnittstelle namens Cloneable. In der Praxis sollten wir diese Schnittstelle implementieren, wenn wir ein Objekt erstellen möchten Cloneable. Object.cloneist geschützt , daher müssen wir es mit einer öffentlichen Methode überschreiben, damit es zugänglich ist.
  2. Ein weiteres Problem tritt auf, wenn wir versuchen , ein komplexes Objekt tief zu kopieren . Angenommen, die clone()Methode aller Mitgliedsobjektvariablen kopiert auch tief, dies ist zu riskant für eine Annahme. Sie müssen den Code in allen Klassen steuern.

Zum Beispiel hat org.apache.commons.lang.SerializationUtils eine Methode für Deep Clone unter Verwendung der Serialisierung ( Quelle ). Wenn wir Bean klonen müssen, gibt es in org.apache.commons.beanutils ( Source ) einige Dienstprogrammmethoden .

  • cloneBean Klont eine Bean basierend auf den verfügbaren Eigenschafts-Gettern und -Setzern, auch wenn die Bean-Klasse selbst Cloneable nicht implementiert.
  • copyProperties Kopiert Eigenschaftswerte von der Ursprungs-Bean in die Ziel-Bean für alle Fälle, in denen die Eigenschaftsnamen identisch sind.
Chandra Sekhar
quelle
1
Können Sie bitte erklären, was in einem anderen Objekt enthalten ist?
Freakyuser
1
@Chandra Sekhar "Flaches Kopieren erstellt eine neue Instanz derselben Klasse und kopiert alle Felder in die neue Instanz und gibt sie zurück." Das ist falsch, um alle Felder zu erwähnen. BCZ-Objekte werden nicht kopiert, nur die Referenzen werden kopiert, auf die verwiesen wird das gleiche Objekt, auf das das alte (Original) zeigte.
JAVA
4
@sunny - Chandras Beschreibung ist korrekt. Und so ist Ihre Beschreibung dessen, was passiert; Ich sage, dass Sie die Bedeutung von "kopiert alle Felder" falsch verstehen. Das Feld ist die Referenz, es ist nicht das Objekt, auf das verwiesen wird. "Alle Felder kopieren" bedeutet "Alle diese Referenzen kopieren". Es ist gut, dass Sie darauf hingewiesen haben, was genau dies für alle anderen bedeutet, die die gleiche Fehlinterpretation wie Sie haben, die Aussage "Alle Felder kopieren". :)
ToolmakerSteve
2
... Wenn wir in einer untergeordneten OO-Sprache mit "Zeigern" auf Objekte denken, würde ein solches Feld die Adresse im Speicher enthalten (z. B. "0x70FF1234"), unter der sich die Objektdaten befinden. Diese Adresse ist der "Feldwert", der kopiert (zugewiesen) wird. Sie haben Recht, dass das Endergebnis ist, dass beide Objekte Felder haben, die auf dasselbe Objekt verweisen (auf dieses zeigen).
ToolmakerSteve
127

Im Paket import org.apache.commons.lang.SerializationUtils;gibt es eine Methode:

SerializationUtils.clone(Object);

Beispiel:

this.myObjectCloned = SerializationUtils.clone(this.object);
Pacheco
quelle
59
Solange das Objekt implementiertSerializable
Androiderson
2
In diesem Fall hat das geklonte Objekt keinen Verweis auf das Original, wenn das letzte statisch ist.
Dante
8
Eine Bibliothek von Drittanbietern, nur um Objekte zu klonen!
Khan
2
@ Khan, "eine Drittanbieter-Bibliothek nur zu" ist eine völlig separate Diskussion! : D
Charles Wood
103

Folgen Sie einfach wie folgt:

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

und wo immer Sie ein anderes Objekt erhalten möchten, führen Sie einfach das Klonen durch. z.B:

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
quelle
1
Hast du das getestet? Ich könnte dies für mein Projekt verwenden und es ist wichtig, korrekt zu sein.
nebligen
2
@misty Ich habe es getestet. Funktioniert perfekt auf meiner Produktions-App
Andrii Kovalchuk
Wenn Sie nach dem Klonen das ursprüngliche Objekt ändern, wird auch der Klon geändert.
Sibish
4
Dies ist insofern falsch, als es sich nicht um eine tiefe Kopie handelt, nach der gefragt wurde.
Bluehorn
1
Diese Methode klont den Zeiger, der auf das klonbare Objekt zeigt, aber alle Eigenschaften in beiden Objekten sind gleich. Es wird also ein neues Objekt im Speicher erstellt, aber die Daten in jedem Objekt sind die gleichen Daten aus dem Speicher
Omar HossamEldin
40

Warum gibt es keine Antwort für die Verwendung der 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;
        }
    }

Es ist wirklich einfach.

BEARBEITEN: Untergeordnetes Objekt über Rekursion einschließen

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
quelle
Dies sieht viel besser aus, aber Sie müssen nur die endgültigen Felder berücksichtigen, da setAccessible (true) möglicherweise fehlschlägt. Daher müssen Sie die Ausnahme IllegalAccessException, die beim Aufruf von field.set (clone, field.get (obj)) ausgelöst wird, möglicherweise separat behandeln.
Max
1
Es hat mir so gut gefallen, aber können Sie es umgestalten, um Generika zu verwenden? private static <T> T cloneObject (T obj) {....}
Adelin
2
Ich denke, es ist ein Problem, wenn wir von Immobilien auf Eltern Bezug nehmen: Class A { B child; } Class B{ A parent; }
nhthai
Es scheitert auch in dieser Situation, muss behandelt werden, ich werde morgen damit spielen. class car { car car = new car(); }
Ján Яabčan
2
Dies ist fehleranfällig. Ich bin mir nicht sicher, wie ich mit Sammlungen umgehen soll
ACV
31

Ich verwende die JSON-Bibliothek von Google, um sie zu serialisieren, und erstelle dann eine neue Instanz des serialisierten Objekts. Es kopiert tief mit ein paar Einschränkungen:

  • Es kann keine rekursiven Referenzen geben

  • Es werden keine Arrays unterschiedlicher Typen kopiert

  • Arrays und Listen sollten eingegeben werden, da sonst die zu instanziierende Klasse nicht gefunden wird

  • Möglicherweise müssen Sie Zeichenfolgen in einer Klasse kapseln, die Sie selbst deklarieren

Ich benutze diese Klasse auch, um Benutzereinstellungen, Fenster und so weiter zu speichern, um zur Laufzeit neu geladen zu werden. Es ist sehr einfach zu bedienen und effektiv.

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;
}
}
Peter
quelle
Das funktioniert super. Aber pass auf, wenn du versuchst, etwas wie List <Integer> zu klonen. Es wird fehlerhaft sein, meine Ganzzahlen wurden in Doppel, 100.0 umgewandelt. Ich habe lange gebraucht, um zu verstehen, warum sie so sind. Die Lösung bestand darin, Ganzzahlen einzeln zu klonen und in einem Zyklus zur Liste hinzuzufügen.
Paakjis
24

Ja, Sie verweisen nur auf das Objekt. Sie können das Objekt klonen, wenn es implementiert wird Cloneable.

Lesen Sie diesen Wiki-Artikel über das Kopieren von Objekten.

Siehe hier: Objektkopieren

Chrisb
quelle
14

Fügen Sie CloneableIhrer Klasse Code hinzu und darunter

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

Benutze das clonedObject = (YourClass) yourClassObject.clone();

Teja Maridu
quelle
12

Ja. Sie müssen Ihr Objekt tief kopieren .

bruno conde
quelle
1
Wie es ist, ist es überhaupt keine Kopie.
Michael Myers
Dies ist wahrscheinlich die am wenigsten hilfreiche Antwort, die ich beim Stackoverflow gesehen habe.
Cyril
12

Das funktioniert auch. Modell annehmen

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

compile 'com.google.code.gson:gson:2.8.1'Fügen Sie zuerst Ihrer App> gradle & sync hinzu. Dann

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

Sie können die Verwendung eines Felds ausschließen, indem Sie das transientSchlüsselwort nach dem Zugriffsmodifikator verwenden.

Hinweis: Dies ist eine schlechte Praxis. Auch nicht empfehlen zu verwenden Cloneableoder JavaSerializationes ist langsam und kaputt. Schreib Copykonstruktor für die beste Leistung ref .

Etwas wie

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

Teststatistik der 90000-Iteration: Die
Linie UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);dauert 808 ms

Die Leitung UserAccount clone = new UserAccount(aO);dauert weniger als 1 ms

Fazit: Verwenden Sie gson, wenn Ihr Chef verrückt ist und Sie Geschwindigkeit bevorzugen. Verwenden Sie den zweiten Kopierkonstruktor, wenn Sie Qualität bevorzugen.

Sie können auch das Plugin zum Kopieren des Konstruktorcode- Generators in Android Studio verwenden.

Qamar
quelle
Warum hast du es vorgeschlagen, wenn es schlechte Praxis ist?
Parth Mehrotra
Danke @ParthMehrotra jetzt verbessert
Qamar
9

Verwenden Sie ein Deep Cloning-Dienstprogramm:

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

Dadurch werden alle Java-Objekte tief kopiert. Überprüfen Sie sie unter https://github.com/kostaskougios/cloning

Cojones
quelle
1
hat bei mir nicht mit einer benutzerdefinierten Klasse funktioniert. die folgende Ausnahme erhalten: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker
9

Deep Cloning ist Ihre Antwort, bei der die CloneableSchnittstelle implementiert und die clone()Methode überschrieben werden muss.

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

Sie werden es so nennen DummyBean dumtwo = dum.clone();

abbas
quelle
2
dummy, a String, ist unveränderlich, Sie müssen es nicht kopieren
Steve Kuo
7

Dazu müssen Sie das Objekt auf irgendeine Weise klonen. Obwohl Java über einen Klonmechanismus verfügt, sollten Sie ihn nicht verwenden, wenn Sie dies nicht müssen. Erstellen Sie eine Kopiermethode, mit der die Kopie für Sie ausgeführt wird, und führen Sie dann Folgendes aus:

dumtwo = dum.copy();

Hier finden Sie weitere Ratschläge zu verschiedenen Techniken zum Erstellen einer Kopie.

Yishai
quelle
6

Abgesehen vom expliziten Kopieren besteht ein anderer Ansatz darin, das Objekt unveränderlich zu machen (keine setoder andere Mutatormethoden). Auf diese Weise stellt sich nie die Frage. Die Unveränderlichkeit wird bei größeren Objekten schwieriger, aber die andere Seite davon ist, dass sie Sie in die Richtung der Aufteilung in zusammenhängende kleine Objekte und Verbundwerkstoffe treibt.

Tom Hawtin - Tackline
quelle
5

Alternative zu egagas Konstruktormethode zum Kopieren. Sie haben wahrscheinlich bereits ein POJO. Fügen Sie einfach eine andere Methode hinzu, copy()die eine Kopie des initialisierten Objekts zurückgibt.

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
}

Wenn Sie bereits eine haben DummyBeanund eine Kopie wünschen:

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

Ausgabe:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Aber beides funktioniert gut, es liegt letztendlich an dir ...

Pierre
quelle
3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
Mahdi Abdi
quelle
3

Sie können mit XStream automatisch von http://x-stream.github.io/ tief kopieren :

XStream ist eine einfache Bibliothek zum Serialisieren von Objekten in XML und wieder zurück.

Fügen Sie es Ihrem Projekt hinzu (wenn Sie maven verwenden)

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

Dann

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

Damit haben Sie eine Kopie, ohne dass Sie eine Klonschnittstelle implementieren müssen.

Jaime Hablutzel
quelle
29
Das Konvertieren in / aus XML scheint nicht sehr ... elegant zu sein. Um es milde auszudrücken!
Timmmm
Suchen Sie nach java.beans.XMLEncodereiner Standard-Java-API, die auch in XML serialisiert wird (allerdings nicht genau für Deep-Copy-Zwecke).
Jaime Hablutzel
1
Weißt du, wie schwer das ist?
Mahieddine
1
Meiner Meinung nach viel zu viel Aufwand, da Sie eine Bibliothek eines Drittanbieters hinzufügen und eine Objektserialisierung durchführen müssen, was höchstwahrscheinlich einen enormen Einfluss auf die Leistung hat.
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();
   }

  }
}

und in Ihrem Code:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();
Amir Hossein Ghasemi
quelle
2
Es hat keinen Sinn, in der Deklaration "löst CloneNotSupportedException aus", wenn Sie versuchen, die Ausnahme abzufangen, und sie wird nicht ausgelöst. Sie können es also einfach entfernen.
Christian
2

Übergeben Sie das Objekt, das Sie kopieren möchten, und erhalten Sie das gewünschte Objekt:

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;

    }

Analysieren Sie nun das objDestgewünschte Objekt.

Viel Spaß beim Codieren!

A-Droid Tech
quelle
1

Sie können versuchen, Cloneabledie clone()Methode zu implementieren und zu verwenden . aber wenn Sie die Klon - Methode Sie sollten - durch Standard - IMMER überschreiben Object‚s - public Object clone()Methode.


quelle
1

Wenn Sie der Quelldatei eine Anmerkung hinzufügen können, kann ein Anmerkungsprozessor oder ein Codegenerator wie dieser verwendet werden.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Es DummyBeanBuilderswird eine Klasse generiert, die über eine statische Methode dummyBeanUpdaterzum Erstellen flacher Kopien verfügt, genauso wie Sie es manuell tun würden.

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

Verwenden Sie gsonfür ein Objekt zu duplizieren.

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

Gehe ich davon aus, ein Objekt haben person.So

Person copyPerson = copyObject(person);
tuhin47
quelle