>  기사  >  Java  >  Java의 전체 복사 및 얕은 복사 소개

Java의 전체 복사 및 얕은 복사 소개

高洛峰
高洛峰원래의
2017-01-19 11:36:201037검색

1. 소개
객체 복사는 객체의 속성을 동일한 클래스 유형의 다른 객체에 복사하는 것입니다. 프로그램에서 객체를 복사하는 것은 매우 일반적이며, 주로 새로운 컨텍스트에서 객체 데이터의 일부 또는 전부를 재사용합니다. Java에는 Shallow Copy, Deep Copy 및 Lazy Copy의 세 가지 유형의 객체 복사본이 있습니다.

2. 얕은 복사

1. 얕은 복사란 무엇인가요?
얕은 복사는 원본의 복사본을 포함하는 새 객체를 생성합니다. 객체의 속성 값. 속성이 기본 유형이면 기본 유형의 값이 복사되고, 속성이 메모리 주소(참조 유형)이면 메모리 주소가 복사되므로 객체 중 하나가 이 주소를 변경하면 다른 객체에도 영향을 미칩니다. 물체.

Java의 전체 복사 및 얕은 복사 소개

그림에서 SourceObject에는 int 유형 속성 "field1"과 참조 유형 속성 "refObj"(ContainedObject 유형의 객체 참조)가 있습니다. SourceObject의 단순 복사본을 만들 때 "field1"의 복사된 값과 여전히 refObj 자체를 가리키는 참조를 포함하는 "field2" 속성을 가진 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
Cloned Object: John - Algebra
Is Original Object is same with Cloned Object: false
Original Object의 필드 이름이 Cloned Object와 동일합니까: true
Original Object의 필드 subj가 Cloned와 동일합니까? 객체: true
업데이트 후 원본 객체: Dan - Physics
원본 객체를 업데이트한 후 복제된 객체: John - Physics

이 예에서는 Student 클래스가 Clonable을 구현하도록 복사했습니다. 인터페이스를 만들고 Object 클래스의 clone() 메서드를 다시 작성한 다음 메서드 내에서 super.clone() 메서드를 호출합니다. 출력 결과에서 원본 개체 Stud의 "name" 속성에 대한 변경 사항은 복사된 개체 clonedStud에 영향을 미치지 않았지만 참조 개체 subj의 "name" 속성에 대한 변경 사항은 복사된 개체에 영향을 미쳤음을 알 수 있습니다. 복제된스터드.

3. Deep Copy
1. Deep Copy란 무엇입니까
Deep Copy는 모든 속성을 복사하고 해당 속성이 가리키는 동적으로 할당된 메모리를 복사합니다. 전체 복사는 객체가 참조하는 객체와 함께 복사될 때 발생합니다. 전체 복사는 얕은 복사보다 느리고 비용이 더 많이 듭니다.

Java의 전체 복사 및 얕은 복사 소개

위 그림에서 SourceObject에는 int 유형 속성 "field1"과 참조 유형 속성 "refObj1"(ContainedObject 유형의 객체 참조)이 있습니다. SourceObject의 전체 복사를 수행하면 "field1"의 복사된 값이 포함된 "field2" 속성과 "refObj1"의 복사된 값이 포함된 참조 유형 속성 "refObj2"가 있는 CopiedObject가 생성됩니다. 따라서 SourceObject의 "refObj"를 변경해도 CopiedObject에는 영향을 미치지 않습니다.

2. Deep Copy 구현 방법
다음은 Deep Copy 구현 예입니다. 얕은 복사 예제에서는 약간만 변경되었으며 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 - Physics
원본 개체를 업데이트한 후 복제된 개체: John - 대수학

clone() 메소드에서 약간의 변화를 발견하기 쉽습니다. Deep Copy이기 때문에 Copy 클래스의 객체를 생성해야 합니다. Student 클래스에 개체 참조가 있으므로 Cloneable 인터페이스를 구현하고 Student 클래스에서 복제 메서드를 재정의해야 합니다.

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으로 문의하세요.