Heim  >  Artikel  >  Java  >  Einführung in Deep Copy und Shallow Copy in Java

Einführung in Deep Copy und Shallow Copy in Java

高洛峰
高洛峰Original
2017-01-19 11:36:201037Durchsuche

1. Einführung
Beim Kopieren von Objekten werden die Eigenschaften eines Objekts in ein anderes Objekt desselben Klassentyps kopiert. Es ist sehr üblich, Objekte in einem Programm zu kopieren, hauptsächlich um einige oder alle Daten des Objekts in einem neuen Kontext wiederzuverwenden. In Java gibt es drei Arten von Objektkopien: Shallow Copy, Deep Copy und Lazy Copy.

2. Flache Kopie

1. Was ist eine flache Kopie?
Eine flache Kopie ist eine bitweise Kopie eines Objekts Attributwerte des Objekts. Wenn es sich bei dem Attribut um einen Basistyp handelt, wird der Wert des Basistyps kopiert. Wenn es sich bei dem Attribut um eine Speicheradresse (Referenztyp) handelt, wird die Speicheradresse kopiert. Wenn also eines der Objekte diese Adresse ändert, wirkt sich dies auf das andere aus Objekt.

Einführung in Deep Copy und Shallow Copy in Java

In der Abbildung verfügt SourceObject über ein int-Typattribut „field1“ und ein Referenztypattribut „refObj“ (bezogen auf ein Objekt vom Typ ContainedObject). Beim Erstellen einer flachen Kopie des SourceObject wird ein CopiedObject erstellt, das über eine Eigenschaft „field2“ verfügt, die den kopierten Wert von „field1“ enthält, und eine Referenz, die immer noch auf refObj selbst verweist. Da es sich bei „field1“ um einen Basistyp handelt, wird sein Wert einfach nach „field2“ kopiert. Da „refObj“ jedoch ein Referenztyp ist, zeigt CopiedObject auf dieselbe Adresse wie „refObj“. Daher wirken sich alle an „refObj“ in SourceObject vorgenommenen Änderungen auf CopiedObject aus.

2. So implementieren Sie eine flache Kopie

Das Folgende ist ein Beispiel für die Implementierung einer flachen Kopie

public class Subject {
    private String name; 
   public Subject(String s) { 
      name = s; 
   } 
   public String getName() { 
      return name; 
   } 
   public void setName(String s) { 
      name = s; 
   } 
}
public class Student implements Cloneable { 
    // 对象引用 
   private Subject subj; 

   private String name; 

   public Student(String s, String sub) { 
      name = s; 
      subj = new Subject(sub); 
   } 

   public Subject getSubj() { 
      return subj; 
   } 

   public String getName() { 
      return name; 
   } 

   public void setName(String s) { 
      name = s; 
   } 

   /** 
    *  重写clone()方法 
    * @return 
    */ 
   public Object clone() { 
      //浅拷贝 
      try { 
         // 直接调用父类的clone()方法
         return super.clone(); 
      } catch (CloneNotSupportedException e) { 
         return null; 
      } 
   } 
}
public class CopyTest {
    public static void main(String[] args) {
        // 原始对象
        Student stud = new Student("John", "Algebra");
        System.out.println("Original Object: " + stud.getName() + " - " + stud.getSubj().getName());
        // 拷贝对象
        Student clonedStud = (Student) stud.clone();
        System.out.println("Cloned Object: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
        // 原始对象和拷贝对象是否一样:
        System.out.println("Is Original Object the same with Cloned Object: " + (stud == clonedStud));
        // 原始对象和拷贝对象的name属性是否一样
        System.out.println("Is Original Object's field name the same with Cloned Object: " +
     (stud.getName() == clonedStud.getName()));
        // 原始对象和拷贝对象的subj属性是否一样
        System.out.println("Is Original Object's field subj the same with Cloned Object: " +
    (stud.getSubj() == clonedStud.getSubj()));
        stud.setName("Dan");
        stud.getSubj().setName("Physics");
        System.out.println("Original Object after it is updated: " + stud.getName() + " - " +
     stud.getSubj().getName());
        System.out.println("Cloned Object after updating original object: " + clonedStud.getName() +
     " - " + clonedStud.getSubj().getName());
    }
}

Das Ausgabeergebnis ist wie folgt:
Originalobjekt: John – Algebra
Geklontes Objekt: John – Algebra
Ist das ursprüngliche Objekt dasselbe wie das geklonte Objekt: false
Ist der Feldname des Originalobjekts derselbe wie das geklonte Objekt: true
Ist das Feld subj des Originalobjekts dasselbe? mit geklontem Objekt: true
Originalobjekt nach Aktualisierung: Dan – Physik
Geklontes Objekt nach Aktualisierung Originalobjekt: John – Physik

In diesem Beispiel lasse ich die zu kopierende Klasse Student implementieren die Clonable-Schnittstelle und schreiben Sie die clone()-Methode der Object-Klasse neu und rufen Sie dann die super.clone()-Methode innerhalb der Methode auf. Aus den Ausgabeergebnissen können wir ersehen, dass sich die am Attribut „name“ des ursprünglichen Objekts stud vorgenommenen Änderungen nicht auf das kopierte Objekt clonedStud ausgewirkt haben, die am Attribut „name“ des Referenzobjekts subj vorgenommenen Änderungen sich jedoch auf das kopierte Objekt ausgewirkt haben clonedStud.

3. Deep Copy
1. Was ist Deep Copy?
Deep Copy kopiert alle Attribute und kopiert den dynamisch zugewiesenen Speicher, auf den die Attribute verweisen. Eine tiefe Kopie liegt vor, wenn ein Objekt zusammen mit den Objekten, auf die es verweist, kopiert wird. Deep Copy ist langsamer und teurer als flaches Kopieren.

Einführung in Deep Copy und Shallow Copy in Java

In der obigen Abbildung verfügt SourceObject über eine int-Typ-Eigenschaft „field1“ und eine Referenztyp-Eigenschaft „refObj1“ (bezieht sich auf ein Objekt vom Typ ContainedObject). Beim Erstellen einer tiefen Kopie des SourceObject wird ein CopiedObject erstellt, das über eine Eigenschaft „field2“ mit dem kopierten Wert von „field1“ und eine Referenztypeigenschaft „refObj2“ mit dem kopierten Wert von „refObj1“ verfügt. Daher wirken sich alle an „refObj“ in SourceObject vorgenommenen Änderungen nicht auf CopiedObject aus

2. So implementieren Sie Deep Copy
Das Folgende ist ein Beispiel für die Implementierung von Deep Copy. Am Beispiel für flache Kopien wurde nur eine kleine Änderung vorgenommen, und die Klassen „Subject“ und „CopyTest“ haben sich nicht geändert.

public class Student implements Cloneable { 
   // 对象引用 
   private Subject subj; 

   private String name; 

   public Student(String s, String sub) { 
      name = s; 
      subj = new Subject(sub); 
   } 

   public Subject getSubj() { 
      return subj; 
   } 

   public String getName() { 
      return name; 
   } 

   public void setName(String s) { 
      name = s; 
   } 

   /** 
    * 重写clone()方法 
    * 
    * @return 
    */ 
   public Object clone() { 
      // 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
      Student s = new Student(name, subj.getName()); 
      return s; 
   } 
}

Die Ausgabeergebnisse lauten wie folgt:
Originalobjekt: John – Algebra
Geklontes Objekt: John – Algebra
Ist das Originalobjekt dasselbe wie das geklonte Objekt: false
Ist der Feldname des Originalobjekts derselbe wie beim geklonten Objekt: true
Ist der Feldname des Originalobjekts derselbe wie beim geklonten Objekt: false
Originalobjekt nach der Aktualisierung: Dan - Physik
Geklontes Objekt nach der Aktualisierung des Originalobjekts: John - Algebra

Es ist leicht, eine kleine Änderung in der clone()-Methode zu finden. Da es sich um eine tiefe Kopie handelt, müssen Sie ein Objekt der Kopierklasse erstellen. Da es in der Student-Klasse eine Objektreferenz gibt, müssen Sie die Cloneable-Schnittstelle implementieren und die Klonmethode in der Student-Klasse überschreiben.

3、通过序列化实现深拷贝
    也可以通过序列化来实现深拷贝。序列化是干什么的?它将整个对象图写入到一个持久化存储文件中并且当需要的时候把它读取回来, 这意味着当你需要把它读取回来时你需要整个对象图的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象图中所有类都是可序列化的。

public class ColoredCircle implements Serializable { 
   private int x; 
   private int y; 

   public ColoredCircle(int x, int y) { 
      this.x = x; 
      this.y = y; 
   } 

   public int getX() { 
      return x; 
   } 

   public void setX(int x) { 
      this.x = x; 
   } 

   public int getY() { 
      return y; 
   } 

   public void setY(int y) { 
      this.y = y; 
   } 

   @Override 
   public String toString() { 
      return "x=" + x + ", y=" + y; 
   } 
}
public class DeepCopy {

   public static void main(String[] args) throws IOException { 
      ObjectOutputStream oos = null; 
      ObjectInputStream ois = null; 

      try { 
         // 创建原始的可序列化对象 
         ColoredCircle c1 = new ColoredCircle(100, 100); 
         System.out.println("Original = " + c1); 

         ColoredCircle c2 = null; 

         // 通过序列化实现深拷贝 
         ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
         oos = new ObjectOutputStream(bos); 
         // 序列化以及传递这个对象 
         oos.writeObject(c1); 
         oos.flush(); 
         ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); 
         ois = new ObjectInputStream(bin); 
         // 返回新的对象 
         c2 = (ColoredCircle) ois.readObject(); 

         // 校验内容是否相同 
         System.out.println("Copied   = " + c2); 
         // 改变原始对象的内容 
         c1.setX(200); 
         c1.setY(200); 
         // 查看每一个现在的内容 
         System.out.println("Original = " + c1); 
         System.out.println("Copied   = " + c2); 
      } catch (Exception e) { 
         System.out.println("Exception in main = " + e); 
      } finally { 
         oos.close(); 
         ois.close(); 
      } 
   } 
}

输出结果如下:
 Original = x=100, y=100 
 Copied = x=100, y=100 
 Original = x=200, y=200 
 Copied = x=100, y=100
这里,你只需要做以下几件事儿:
(1)确保对象图中的所有类都是可序列化的
(2)创建输入输出流
(3)使用这个输入输出流来创建对象输入和对象输出流
(4)将你想要拷贝的对象传递给对象输出流
(5)从对象输入流中读取新的对象并且转换回你所发送的对象的类

    在这个例子中,我创建了一个ColoredCircle对象c1然后将它序列化 (将它写到ByteArrayOutputStream中). 然后我反序列化这个序列化后的对象并将它保存到c2中。随后我修改了原始对象c1。然后结果如你所见,c1不同于c2,对c1所做的任何修改都不会影响c2。

注意,序列化这种方式有其自身的限制和问题:
因为无法序列化transient变量, 使用这种方法将无法拷贝transient变量。

   再就是性能问题。创建一个socket, 序列化一个对象, 通过socket传输它, 然后反序列化它,这个过程与调用已有对象的方法相比是很慢的。所以在性能上会有天壤之别。如果性能对你的代码来说是至关重要的,建议不要使用这种方式。它比通过实现Clonable接口这种方式来进行深拷贝几乎多花100倍的时间。

四、延迟拷贝
   延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。 当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。 
   延迟拷贝从外面看起来就是深拷贝,但是只要有可能它就会利用浅拷贝的速度。当原始对象中的引用不经常改变的时候可以使用延迟拷贝。由于存在计数器,效率下降很高,但只是常量级的开销。而且, 在某些情况下, 循环引用会导致一些问题。

五、如何选择
  如果对象的属性全是基本类型的,那么可以使用浅拷贝,但是如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。我的意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。

更多Einführung in Deep Copy und Shallow Copy in Java相关文章请关注PHP中文网!

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