Heim  >  Artikel  >  Java  >  Detailliertes Beispiel für das Kopieren von Java-Objekten

Detailliertes Beispiel für das Kopieren von Java-Objekten

Y2J
Y2JOriginal
2017-05-02 14:15:001977Durchsuche

In diesem Artikel werden hauptsächlich detaillierte Erklärungen und Beispiele für das Kopieren von Java-Objekten vorgestellt. Freunde, die es benötigen, können sich auf

Detaillierte Erklärungen und Beispiele für das Kopieren von Java-Objekten beziehen

Die Java-Zuweisung kopiert eine Objektreferenz. Wenn wir eine Kopie eines Objekts erhalten möchten, kann die Verwendung der Zuweisungsoperation den Zweck nicht erreichen:

@Test
public void testassign(){
 Person p1=new Person();
 p1.setAge(31);
 p1.setName("Peter");

 Person p2=p1;
 System.out.println(p1==p2);//true
}

Wenn Sie eine neue Kopie des Objekts erstellen, d. h Sagen wir, ihr Anfangszustand ist genau derselbe, aber Sie können ihren jeweiligen Zustand in Zukunft ändern, ohne sich gegenseitig zu beeinflussen. Daher müssen Sie die Kopie von Objekten in Java verwenden, z. B. die native Methode clone().

So klonen Sie ein Objekt

Das Object-Objekt verfügt über eine clone()-Methode, die das Kopieren jedes Attributs im Objekt realisiert, sein sichtbarer Bereich ist jedoch geschützt , also Die Voraussetzung für die Verwendung des Klonens für Entitätsklassen ist:

① Implementieren Sie die Cloneable-Schnittstelle, die eine Markierungsschnittstelle ist und keine eigenen Methoden hat.

② Überschreiben Sie die clone()-Methode und erhöhen Sie die Sichtbarkeit für die Öffentlichkeit.

@Data
public class Person implements Cloneable {
  private String name;
  private Integer age;
  private Address address;
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

@Test
public void testShallowCopy() throws Exception{
 Person p1=new Person();
 p1.setAge(31);
 p1.setName("Peter");

 Person p2=(Person) p1.clone();
 System.out.println(p1==p2);//false
 p2.setName("Jacky");
 System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]
 System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]
}

Dieser Testfall hat nur zwei Grundtypen von Mitgliedern, und der Test erfüllt seinen Zweck.

Die Dinge scheinen nicht so einfach zu sein. Fügen Sie ein Mitglied der Address-Klasse zu Person hinzu:

@Data
public class Address {
  private String type;
  private String value;
}

Testen Sie es erneut, und das Problem tritt auf.

@Test
public void testShallowCopy() throws Exception{
 Address address=new Address();
 address.setType("Home");
 address.setValue("北京");

 Person p1=new Person();
 p1.setAge(31);
 p1.setName("Peter");
 p1.setAddress(address);

 Person p2=(Person) p1.clone();
 System.out.println(p1==p2);//false

 p2.getAddress().setType("Office");
 System.out.println("p1="+p1);
 System.out.println("p2="+p2);
}

Sehen Sie sich die Ausgabe an:

false
p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

Ich hatte ein Problem. Ich habe nur den Adresstyp von p2 geändert und beide Adresstypen wurden zu Office.

Shallow Copy und Deep Copy

Die vorherigen Beispiele sind typische Anwendungsfälle von Shallow Copy und Deep Copy.

Flache Kopie: Alle Wertattribute des kopierten Objekts enthalten dieselben Werte wie die des Originalobjekts, und alle Objektreferenzattribute verweisen weiterhin auf das Originalobjekt.

Tiefe Kopie: Auf der Grundlage der flachen Kopie werden alle Variablen, die auf andere Objekte verweisen, ebenfalls geklont und verweisen auf das kopierte neue Objekt.

Mit anderen Worten, ein Standardmechanismus zur Implementierung der clone()-Methode ist immer noch eine Zuweisung.

Wenn ein kopiertes Attribut ein Basistyp ist, müssen Sie nur den klonbaren Mechanismus der aktuellen Klasse implementieren. Dies ist eine flache Kopie.

Wenn die Eigenschaften des kopierten Objekts Verweise auf andere Entitätsklassenobjekte enthalten, müssen diese Entitätsklassenobjekte die klonbare Schnittstelle implementieren und die clone()-Methode überschreiben.

@Data
public class Address implements Cloneable {
  private String type;
  private String value;

  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

Das reicht nicht aus, Person's clone() muss seine Referenzmitglieder explizit klonen.

@Data
public class Person implements Cloneable {
  private String name;
  private Integer age;
  private Address address;
  @Override
  protected Object clone() throws CloneNotSupportedException {
    Object obj=super.clone();
    Address a=((Person)obj).getAddress();
    ((Person)obj).setAddress((Address) a.clone());
    return obj;
  }
}

Führen Sie den vorherigen Testfall erneut aus:

false
p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

Zusammenfassung der Deep-Copy-in-Clone-Methode

① Wenn es eine Nicht- natives Mitglied, Wenn Sie ein Mitglied eines benutzerdefinierten Objekts definieren, benötigen Sie:

  1. Das Mitglied implementiert die Cloneable-Schnittstelle und überschreibt die clone()-Methode öffentliche Sichtbarkeit.

  2. Ändern Sie gleichzeitig die clone()-Methode der kopierten Klasse und fügen Sie die Klonlogik für Mitglieder hinzu.

② Wenn das kopierte Objekt Object nicht direkt erbt, gibt es dazwischen andere Vererbungsebenen. Jede Superklasse muss die Cloneable-Schnittstelle implementieren und die clone()-Methode überschreiben.

Im Gegensatz zu Objektmitgliedern erfordert das Klonen in einer Vererbungsbeziehung keine redundante Arbeit durch clone() der kopierten Klasse.

Kurz gesagt: Wenn Sie eine vollständige Tiefenkopie implementieren, muss jedes Objekt in der Vererbungskette und Referenzkette des kopierten Objekts den Klonmechanismus implementieren.

Das vorherige Beispiel ist akzeptabel, aber wenn es N Objektmitglieder und Vererbungsbeziehungen auf M-Ebene gibt, wird es sehr problematisch sein.

Serialisierung zur Implementierung von Deep Copy verwenden

Der Klonmechanismus wird nicht durch starke Typen eingeschränkt. Beispielsweise erzwingt die Implementierung von Cloneable die Objekte in der Vererbungskette muss ebenfalls implementiert werden; es gibt keine Kraft. Erfordert das Überschreiben der clone()-Methode. Daher kann es während des Codierungsprozesses leicht passieren, dass einer der Links übersehen wird, was die Fehlersuche bei komplexen Projekten erschwert.

Wenn Sie nach zuverlässigen, einfachen Methoden suchen, ist die Serialisierung die richtige Wahl.

1. Jedes Objekt in der Vererbungskette und Referenzkette des kopierten Objekts implementiert die java.io.Serializable-Schnittstelle. Dies ist relativ einfach und erfordert keine Implementierung von Methoden. Die Anforderung „serialVersionID“ ist nicht zwingend erforderlich und reicht für tiefes Kopieren aus.

2. Implementieren Sie Ihre eigene deepClone-Methode, schreiben Sie diese in den Stream und lesen Sie sie dann aus. Allgemein bekannt als: Gefrier-Tau-Wechsel.

@Data
public class Person implements Serializable {
  private String name;
  private Integer age;
  private Address address;
  public Person deepClone() {
    Person p2=null;
    Person p1=this;
    PipedOutputStream out=new PipedOutputStream();
    PipedInputStream in=new PipedInputStream();
    try {
      in.connect(out);
    } catch (IOException e) {
      e.printStackTrace();
    }

    try(ObjectOutputStream bo=new ObjectOutputStream(out);
        ObjectInputStream bi=new ObjectInputStream(in);) {
      bo.writeObject(p1);
      p2=(Person) bi.readObject();

    } catch (Exception e) {
      e.printStackTrace();
    }
    return p2;
  }
}

Prototyp-Fabrikklasse

Um das Testen zu erleichtern und Platz zu sparen, ist eine Fabrikklasse gekapselt.

Vermeiden Sie der Fairness halber die Verwendung von Caching-Mechanismen in einigen Werkzeugbibliotheken und verwenden Sie Prototypenfabriken.

public class PersonFactory{
  public static Person newPrototypeInstance(){
    Address address = new Address();
    address.setType("Home");
    address.setValue("北京");

    Person p1 = new Person();
    p1.setAddress(address);
    p1.setAge(31);
    p1.setName("Peter");
    return p1;
  }
}

Verwenden Sie Dozer, um Objekte zu kopieren

Dozer ist eine Bean-Verarbeitungsbibliothek.

Maven-Abhängigkeit

<dependency>
 <groupId>net.sf.dozer</groupId>
 <artifactId>dozer</artifactId>
 <version>5.5.1</version>
</dependency>

Testfall:

@Data
public class Person {
  private String name;
  private Integer age;
  private Address address;

  @Test
  public void testDozer() {
  Person p1=PersonFactory.newPrototypeInstance();
    Mapper mapper = new DozerBeanMapper();
    Person p2 = mapper.map(p1, Person.class);
    p2.getAddress().setType("Office");
    System.out.println("p1=" + p1);
    System.out.println("p2=" + p2);
  }
}

@Data
public class Address {
  private String type;
  private String value;
}

Ausgabe:

p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京))
p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))

Hinweis: In 10.000 Tests gibt es einen Dozer A Sehr ernstes Problem. Wenn das DozerBeanMapper-Objekt in einer for-Schleife erstellt wird, wird die Effizienz (dozer:7358) um ​​fast das Zehnfache reduziert. Da DozerBeanMapper threadsicher ist, sollte nicht jedes Mal eine neue Instanz erstellt werden. Sie können die integrierte Singleton-Factory DozerBeanMapperSingletonWrapper verwenden, um einen Mapper zu erstellen oder ihn in Spring zu integrieren.

Es gibt noch mehr Gewalt, erstellen Sie eine People-Klasse:

@Data
public class People {
  private String name;
  private String age;//这里已经不是Integer了
  private Address address;

  @Test
  public void testDozer() {
  Person p1=PersonFactory.newPrototypeInstance();
    Mapper mapper = new DozerBeanMapper();
    People p2 = mapper.map(p1, People.class);
    p2.getAddress().setType("Office");
    System.out.println("p1=" + p1);
    System.out.println("p2=" + p2);
  }
}

Solange die Attributnamen gleich sind, tun Sie es ~

Weiter verwüsten:

@Data
public class People {
  private String name;
  private String age;
  private Map<String,String> address;//��

  @Test
  public void testDozer() {
  Person p1=PersonFactory.newPrototypeInstance();
    Mapper mapper = new DozerBeanMapper();
    People p2 = mapper.map(p1, People.class);
    p2.getAddress().put("type", "Office");
    System.out.println("p1=" + p1);
    System.out.println("p2=" + p2);
  }
}

Verwenden Sie Commons-BeanUtils, um Objekte zu kopieren

Maven-Abhängigkeit

<dependency>
 <groupId>commons-beanutils</groupId>
 <artifactId>commons-beanutils</artifactId>
 <version>1.9.3</version>
</dependency>

Testfall:

@Data
public class Person {
  private String name;
  private String age;
  private Address address;

  @Test
  public void testCommonsBeanUtils(){
  Person p1=PersonFactory.newPrototypeInstance();
    try {
      Person p2=(Person) BeanUtils.cloneBean(p1);
      System.out.println("p1=" + p1);
      p2.getAddress().setType("Office");
      System.out.println("p2=" + p2);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Verwenden Sie cglib, um Objekte zu kopieren

Maven-Abhängigkeit:

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.2.4</version>
</dependency>

Testfall:

@Test
public void testCglib(){
 Person p1=PersonFactory.newPrototypeInstance();
 BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
 Person p2=new Person();
 beanCopier.copy(p1, p2,null);
 p2.getAddress().setType("Office");
 System.out.println("p1=" + p1);
 System.out.println("p2=" + p2);
}

Das Ergebnis ist schockierend, cglib ist so großartig , es ist eigentlich eine oberflächliche Kopie. cglib bietet jedoch Erweiterungsmöglichkeiten:

@Test
public void testCglib(){
 Person p1=PersonFactory.newPrototypeInstance();
 BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
 Person p2=new Person();
 beanCopier.copy(p1, p2, new Converter(){
  @Override
  public Object convert(Object value, Class target, Object context) {
   if(target.isSynthetic()){
    BeanCopier.create(target, target, true).copy(value, value, this);
   }
   return value;
  }
 });
 p2.getAddress().setType("Office");
 System.out.println("p1=" + p1);
 System.out.println("p2=" + p2);
}

Orika复制对象

orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。

maven依赖:

<dependency>
 <groupId>ma.glasnost.orika</groupId>
 <artifactId>orika-core</artifactId>
 <version>1.5.0</version>
</dependency>
</dependencies>

测试用例:

@Test
public void testOrika() {
 MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

 mapperFactory.classMap(Person.class, Person.class)
 .byDefault()
 .register();
 ConverterFactory converterFactory = mapperFactory.getConverterFactory();
 MapperFacade mapper = mapperFactory.getMapperFacade();

 Person p1=PersonFactory.newPrototypeInstance();
 Person p2 = mapper.map(p1, Person.class);
 System.out.println("p1=" + p1);
 p2.getAddress().setType("Office");
 System.out.println("p2=" + p2);
}

Spring BeanUtils复制对象

给Spring个面子,貌似它不支持深拷贝。

Person p1=PersonFactory.newPrototypeInstance();
Person p2 = new Person();
Person p2 = (Person) BeanUtils.cloneBean(p1);
//BeanUtils.copyProperties(p2, p1);//这个更没戏

深拷贝性能对比

@Test
public void testBatchDozer(){
 Long start=System.currentTimeMillis();
 Mapper mapper = new DozerBeanMapper();
 for(int i=0;i<10000;i++){
  Person p1=PersonFactory.newPrototypeInstance();
  Person p2 = mapper.map(p1, Person.class);
 }
 System.out.println("dozer:"+(System.currentTimeMillis()-start));
 //dozer:721
}
@Test
public void testBatchBeanUtils(){
 Long start=System.currentTimeMillis();
 for(int i=0;i<10000;i++){
  Person p1=PersonFactory.newPrototypeInstance();
  try {
   Person p2=(Person) BeanUtils.cloneBean(p1);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start));
 //commons-beanutils:229
}
@Test
public void testBatchCglib(){
 Long start=System.currentTimeMillis();
 for(int i=0;i<10000;i++){
  Person p1=PersonFactory.newPrototypeInstance();
  BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
  Person p2=new Person();
  beanCopier.copy(p1, p2, new Converter(){
   @Override
   public Object convert(Object value, Class target, Object context) {
    if(target.isSynthetic()){
     BeanCopier.create(target, target, true).copy(value, value, this);
    }
    return value;
   }
  });
 }
 System.out.println("cglib:"+(System.currentTimeMillis()-start));
 //cglib:133
}
@Test
public void testBatchSerial(){
 Long start=System.currentTimeMillis();
 for(int i=0;i<10000;i++){
  Person p1=PersonFactory.newPrototypeInstance();
  Person p2=p1.deepClone();
 }
 System.out.println("serializable:"+(System.currentTimeMillis()-start));
 //serializable:687
}
@Test
public void testBatchOrika() {
 MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

 mapperFactory.classMap(Person.class, Person.class)
 .field("name", "name")
 .byDefault()
 .register();
 ConverterFactory converterFactory = mapperFactory.getConverterFactory();
 MapperFacade mapper = mapperFactory.getMapperFacade();

 Long start=System.currentTimeMillis();
 for(int i=0;i<10000;i++){
  Person p1=PersonFactory.newPrototypeInstance();
  Person p2 = mapper.map(p1, Person.class);
 }
 System.out.println("orika:"+(System.currentTimeMillis()-start));
 //orika:83
}

@Test
public void testBatchClone(){
 Long start=System.currentTimeMillis();
 for(int i=0;i<10000;i++){
  Person p1=PersonFactory.newPrototypeInstance();
  try {
   Person p2=(Person) p1.clone();
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
 }
 System.out.println("clone:"+(System.currentTimeMillis()-start));
 //clone:8
}

(10k)性能比较:

//dozer:721
//commons-beanutils:229
//cglib:133
//serializable:687
//orika:83
//clone:8

深拷贝总结

原生的clone效率无疑是最高的,用脚趾头都能想到。

偶尔用一次,用哪个都问题都不大。

一般性能要求稍高的应用场景,cglib和orika完全可以接受。

另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。

Das obige ist der detaillierte Inhalt vonDetailliertes Beispiel für das Kopieren von Java-Objekten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn