ホームページ  >  記事  >  Java  >  Java のディープ コピーとシャロー コピーの概要

Java のディープ コピーとシャロー コピーの概要

高洛峰
高洛峰オリジナル
2017-01-19 11:36:201090ブラウズ

1. はじめに
オブジェクトのコピーとは、オブジェクトのプロパティを同じクラス型の別のオブジェクトにコピーすることです。プログラム内のオブジェクトをコピーすることは非常に一般的であり、主にオブジェクトのデータの一部またはすべてを新しいコンテキストで再利用します。 Java には、Shallow Copy、Deep Copy、Lazy Copy の 3 種類のオブジェクト コピーがあります。

2. 浅いコピー

1. 浅いコピーとは、元のオブジェクトの属性値の正確なコピーを持つ新しいオブジェクトを作成します。属性が基本型の場合は基本型の値がコピーされ、属性がメモリ アドレス (参照型) の場合はメモリ アドレスがコピーされるため、一方のオブジェクトがこのアドレスを変更すると、もう一方のオブジェクトにも影響します。物体。

Java のディープ コピーとシャロー コピーの概要

この図では、SourceObject は int 型属性「field1」と参照型属性「refObj」(ContainedObject 型のオブジェクトを参照) を持っています。 SourceObject の浅いコピーを作成すると、「field1」のコピーされた値を含むプロパティ「field2」と、引き続き refObj 自体を指す参照を持つ CopiedObject が作成されます。 「field1」は基本型なのでその値が「field2」にコピーされるだけですが、「refObj」は参照型なのでCopiedObjectは「refObj」と同じアドレスを指します。したがって、SourceObject の「refObj」に加えられた変更は、CopiedObject に影響します。

2. 浅いコピーの実装方法

以下は浅いコピーの実装例です

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

出力結果は以下の通りです:

元のオブジェクト: John - Algebra
クローンされたオブジェクト: John - Algebra
元のオブジェクトは同じですかクローンオブジェクトの場合: false
オリジナルオブジェクトのフィールド名はクローンオブジェクトと同じですか: true
オリジナルオブジェクトのフィールドsubjはクローンオブジェクトと同じですか: true
更新後のオリジナルオブジェクト: Dan - Physics
オリジナルオブジェクト更新後のクローンオブジェクト: John - 物理学

この例では、コピーされるクラス Student に Clonable インターフェイスを実装させ、Object クラスの clone() メソッドをオーバーライドし、メソッド内で super.clone() メソッドを呼び出します。出力結果から、元のオブジェクト スタッドの「name」属性に加えられた変更はコピーされたオブジェクト clonedStud には影響しませんでしたが、参照オブジェクト subj の「名前」属性に加えられた変更はコピーされたオブジェクトに影響を与えたことがわかります。クローンスタッド。

3. ディープコピー

1. ディープコピーとは
ディープコピーは、すべての属性をコピーし、その属性が指す動的に割り当てられたメモリをコピーします。ディープ コピーは、オブジェクトが参照するオブジェクトとともにコピーされるときに発生します。ディープ コピーはシャロー コピーよりも遅く、コストが高くなります。

Java のディープ コピーとシャロー コピーの概要

上の図では、SourceObject に int 型属性「field1」と参照型属性「refObj1」(ContainedObject 型のオブジェクトを参照) があります。 SourceObject のディープ コピーを実行すると、コピーされた "field1" の値を含むプロパティ "field2" と、コピーされた "refObj1" の値を含む参照型プロパティ "refObj2" を持つ CopiedObject が作成されます。したがって、SourceObject の "refObj" に加えられた変更は、CopiedObject には影響しません

2. ディープ コピーの実装方法

以下は、ディープ コピーの実装例です。浅いコピーの例には小さな変更のみが加えられており、Subject クラスと CopyTest クラスは変更されていません。

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

出力結果は以下の通りです:

オリジナルオブジェクト: John - Algebra
クローンオブジェクト: John - Algebra
オリジナルオブジェクトはクローンオブジェクトと同じですか: false
オリジナルオブジェクトのフィールド名はクローンオブジェクトと同じですか: true
はです元のオブジェクトのフィールド subj はクローン オブジェクトと同じです: false
更新後の元のオブジェクト: Dan - 物理学
元のオブジェクト更新後のクローン オブジェクト: John - Algebra

clone() メソッドの小さな変更を見つけるのは簡単です。ディープコピーなのでコピークラスのオブジェクトを作成する必要があります。 Student クラスにはオブジェクト参照があるため、Cloneable インターフェイスを実装し、Student クラスの clone メソッドをオーバーライドする必要があります。

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倍的时间。

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

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

更多Java のディープ コピーとシャロー コピーの概要相关文章请关注PHP中文网!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。