代码改变世界

Item 11 谨慎地覆盖Clone

2015-03-25 22:30  ttylinux  阅读(218)  评论(0编辑  收藏  举报

    1.进行浅拷贝时,只是复制原始数据类型的值,则可以通过覆盖Clone方法来达到。另外,在进行浅拷贝的时候,还要注意,成员对象中不应该要有引用类型,如果有引用类型,那么,进行了浅拷贝之后,两个对象将会共享成员引用所指向的对象,这会出现问题。所以,在这种情况下,干脆直接使用深拷贝,避免问题出现。2.对于深拷贝,也就是完全将对象的内容复制一份,则使用序列化来实现,也是为了避免覆盖Clone。

      浅拷贝的例子:

        这个例子,只是复制成员的值,成员的类型都是原始数据类型,不包含引用类型:

public class CloneTest1 {

  public static void main(String[] args) throws CloneNotSupportedException {
    Student student1 = new Student();
    student1.setName("ZhangSan");
    student1.setAge(20);

    Student student2 = new Student();
    student2 = (Student) student1.clone();

    System.out.println("拷贝得到的信息");
    System.out.println(student2.getName());
    System.out.println(student2.getAge());
    System.out.println("-------------");

    // 修改第二个对象的信息
    student2.setName("LiSi");
    student2.setAge(25);

    System.out.println("修改第二个对象的属性为lisi,25后:");
    System.out.println("第一个对象:");
    System.out.println(student1.getName());
    System.out.println(student1.getAge());
    System.out.println("第二个对象:");
    System.out.println(student2.getName());
    System.out.println(student2.getAge());
    System.out.println("-------------");

    // 说明两个引用student1和student2指向的是不同的对象

  }
}


class Student implements Cloneable {
  private String name;
  private int age;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public Student clone() throws CloneNotSupportedException {
    // 注意此处要把protected改为public

    Student student = (Student)super.clone();

    return student;
  }
}

 输出:  

拷贝得到的信息
ZhangSan
20
-------------
修改第二个对象的属性为lisi,25后:
第一个对象:
ZhangSan
20
第二个对象:
LiSi
25
-------------

    浅拷贝例子2:

     这个例子中,成员的类型,有引用类型

     

public class CloneTest2 {
  public static void main(String[] args) throws CloneNotSupportedException {
    Teacher teacher = new Teacher();
    teacher.setName("Teacher Zhang");
    teacher.setAge(40);

    StudentNew student1 = new StudentNew();
    student1.setName("ZhangSan");
    student1.setAge(20);
    student1.setTeacher(teacher);

    StudentNew student2 = (StudentNew) student1.clone();
    System.out.println("拷贝得到的信息");
    System.out.println(student2.getName());
    System.out.println(student2.getAge());
    System.out.println(student2.getTeacher().getName());
    System.out.println(student2.getTeacher().getAge());
    System.out.println("-------------");

    // 修改老师的信息
    teacher.setName("Teacher Zhang has changed");
    System.out.println(student1.getTeacher().getName());
    System.out.println(student2.getTeacher().getName());

    // 两个引用student1和student2指向不同的两个对象
    // 但是两个引用student1和student2中的两个teacher引用指向的是同一个对象
    // 所以说明是浅拷贝
  }

}


class Teacher {
  private String name;
  private int age;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

}


class StudentNew implements Cloneable {
  private String name;
  private int age;
  private Teacher teacher;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public Teacher getTeacher() {
    return teacher;
  }

  public void setTeacher(Teacher teacher) {
    this.teacher = teacher;
  }

  @Override
  public StudentNew clone() throws CloneNotSupportedException {
    StudentNew object = (StudentNew) super.clone();
    return object;
  }

}

  输出结果:

      

拷贝得到的信息
ZhangSan
20
Teacher Zhang
40
-------------
Teacher Zhang has changed
Teacher Zhang has changed

解析:

Student1和Student2,是两个对象,执行浅拷贝之后,两个的成员的值是相等的。如果里面包含了引用类型的成员,那么,它们是指向同一个对象的。比如,引用类型teacher成员。因为指向同一个对象,所以对teacher的修改,会同时影响到Student1,Student2.这会出现很多意料不到的问题。对于这种情况,我倾向于,使用序列化,做一个深拷贝。

 

深拷贝,使用序列化实现的深拷贝:

使用序列化实现深拷贝,优点是,不用覆盖Clone(覆盖Clone方法,把握不好,会出现各种问题);缺点是:类成员如果有类类型,则都要实现序列化接口,这样才可以进行序列化,比如下面的例子。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class JavaDeepClone {

  public static void main(String[] args) {
    // (1) create a Person object named Al
    Address address = new Address("305 West Fern Avenue", "Palmer", "Alaska");
    Person al = new Person("Al", "Alexander", address);

    // (2) make a deep clone of Al
    Person neighbor = (Person) deepClone(al);

    // (3) modify the neighbor's attributes
    neighbor.firstName = "Martha";
    neighbor.lastName = "Stewart";

    // (4) show that it all worked
    System.out.print(neighbor);
    System.out.println(al);
  }

  /**
   * This method makes a "deep clone" of any object it is given.
   */
  public static Object deepClone(Object object) {
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(object);
      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
      ObjectInputStream ois = new ObjectInputStream(bais);
      return ois.readObject();
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
}


/**
 * These classes implement Serializable so we can write them out and read them back in as a stream
 * of bytes.
 */
class Person implements Serializable {
  /**
   * 
   */
  private static final long serialVersionUID = 4112183016776552816L;
  String firstName, lastName;
  Address address;

  public Person(String firstName, String lastName, Address address) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.address = address;
  }

  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("First Name: " + firstName + "\n");
    sb.append("Last Name: " + lastName + "\n");
    sb.append("Street: " + address.street + "\n");
    return sb.toString();
  }
}


class Address implements Serializable {
  /**
   * 
   */
  private static final long serialVersionUID = -3711035200324594412L;
  String street, city, state;

  public Address(String street, String city, String state) {
    this.street = street;
    this.city = city;
    this.state = state;
  }
}

输出结果:

First Name: Martha
Last Name: Stewart
Street: 305 West Fern Avenue
First Name: Al
Last Name: Alexander
Street: 305 West Fern Avenue

 

 

引用:

http://www.cnblogs.com/mengdd/archive/2013/02/20/2917971.html

http://alvinalexander.com/java/java-deep-clone-example-source-code