为什么需要拷贝?

  拷贝的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”的时候,就需要clone()。
  优势:clone()方法由native修饰,在底层实现,速度快。

  注意:Object a= new Object(); Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象;通过clone()方法复制的对象与原来的对象是同时独立存在的。

如何实现拷贝?

  有两种方式:深拷贝(DeepClone)与浅拷贝(ShallowClone)。

浅拷贝
  被复制对象的所有变量都含有与原来的对象相同的值,而所有对其他对象的引用仍然指向原来的对象。对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。“里面的对象”会在原来的对象和它的副本之间共享。简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

一般步骤:
1)被复制的类需要实现Cloneable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常),其中Cloneable接口为标记接口(不含任何方法)。
2)覆盖clone()方法,将访问修饰符设为public。在方法中调用super.clone()方法得到需要的复制对象。

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

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = 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
    protected Object clone()  {
        Student stu = null;
        try {
            stu = (Student)super.clone();//浅拷贝
        }catch (CloneNotSupportedException ex){
            ex.printStackTrace();
        }
    }
}

public class CloneTest{
 //浅拷贝
    @Test
    public void test01(){
        Student stu1 = new Student();
        stu1.setName("Jack");
        stu1.setAge(100);
        Student stu2 = (Student)stu1.clone();
        System.out.println("stu1:"+stu1.getName()+" "+stu1.getAge());
        System.out.println("stu2:"+stu2.getName()+" "+stu2.getAge());
        System.out.println("=================");
        stu2.setName("张明");
        stu2.setAge(88);
        System.out.println("stu1:"+stu1.getName()+" "+stu1.getAge());
        System.out.println("stu2:"+stu2.getName()+" "+stu2.getAge());
        System.out.println("stu1与stu2是否是同一个对象:"+(stu1==stu2));
    }
   }

运行结果:

stu1:Jack 100
stu2:Jack 100
=================
stu1:Jack 100
stu2:张明 88
stu1与stu2是否是同一个对象:false
class Student implements Cloneable {
    private String name;
    private int age;
    private Grade grade;

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = 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;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    @Override
    protected Object clone()  {
        Student stu = null;
        try {
            stu = (Student)super.clone();//浅拷贝
        }catch (CloneNotSupportedException ex){
            ex.printStackTrace();
        }
        return stu;
    }
}

public class CloneTest{
 @Test
    public void test03(){
        Grade grade = new Grade();
        grade.setGrade(88);
        Student stu1 = new Student();
        stu1.setName("Jack");
        stu1.setGrade(grade);
        Student stu2 = (Student)stu1.clone();
        System.out.println("修改前:");
        System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
        System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());

        System.out.println("修改后");
        grade.setGrade(100);
        System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
        System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());
    }
  }

运行结果:

修改前:
Jack 88
Jack 88
修改后:
Jack 100
Jack 100

修改学生的成绩,两个学生的成绩都改变了。原因:

  浅拷贝只是拷贝了grade变量的引用,并没有真正为grade另外开辟一块空间,而是将引用赋给新对象,实际上新对象与就对象指向的还是同一块区域。

java 对象如何进行深拷贝 java深浅拷贝_java 对象如何进行深拷贝

深拷贝

  它是一个整体独立的对象拷贝,会拷贝所有的属性,并拷贝属性所指向的动态分配的内存。当对象和它所引用的对象一起拷贝时发生的是深拷贝。深拷贝相对于浅拷贝来说,速度更慢开销更大。
  简单来说,深拷贝会对它所要拷贝的对象及其对象中的引用都进行拷贝。

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

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = 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;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    @Override
    protected Object clone()  {
        Student stu = null;
        try {
            stu = (Student)super.clone();//浅拷贝
        }catch (CloneNotSupportedException ex){
            ex.printStackTrace();
        }
       stu.grade = (Grade) grade.clone(); //深拷贝
        return stu;
    }
}
public class CloneTest{
  @Test
    public void test03(){
        Grade grade = new Grade();
        grade.setGrade(88);
        Student stu1 = new Student();
        stu1.setName("Jack");
        stu1.setGrade(grade);
        Student stu2 = (Student)stu1.clone();
        System.out.println("修改前:");
        System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
        System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());

        System.out.println("修改后:");
        grade.setGrade(100);
        System.out.println(stu1.getName()+" "+stu1.getGrade().getGrade());
        System.out.println(stu2.getName()+" "+stu2.getGrade().getGrade());
    }
 }

运行结果:

修改前:
Jack 88
Jack 88
修改后:
Jack 100
Jack 88

java 对象如何进行深拷贝 java深浅拷贝_System_02


stu1.grade与stu2.grade指向两个不同的对象。


深拷贝与浅拷贝的实现方式

浅拷贝的二种实现方式:

1、通过拷贝构造方法来实现浅拷贝
  拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。

class Student{
    private String name;
    private int age;
    private Grade grade;

    public Student() {
    }

    public Student(String name, Grade grade) {
        this.name = name;
        this.grade = grade;
    }

    //拷贝构造方法
    public Student(Student stu){
        this.name = stu.name;
        this.grade = stu.grade;
    }

    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 Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class CloneTest{
      //1.通过拷贝构造方法
    @Test
    public void cloneConstructor(){
        Grade grade = new Grade(69);
        Student stu1 = new Student("Jack",grade);
        Student stu2 = new Student(stu1);
        System.out.println("修改前:");
        System.out.println("stu1:"+stu1.getName()+" "+stu1.getGrade().getGrade());
        System.out.println("stu2:"+stu2.getName()+" "+stu2.getGrade().getGrade());
        System.out.println("修改后:");
        stu1.setName("布莱克");
        grade.setGrade(100);
        System.out.println("stu1:"+stu1.getName()+" "+stu1.getGrade().getGrade());
        System.out.println("stu2:"+stu2.getName()+" "+stu2.getGrade().getGrade());

}

运行结果:

修改前:
stu1:Jack 69
stu2:Jack 69
修改后:
stu1:布莱克 100
stu2:Jack 100

结果分析:
  Student类中一共有两种类型的数据:字符串类型(String name,属于常量)和引用数据类型(Grade grade)。
  通过拷贝构造方法进行浅拷贝,对各属性的值都进行了复制。stu1值传递部分的属性值(String name)发生改变时,不会引起stu2的name属性值的改变;stu1引用传递部分的属性值(Grade grade)发生改变时,stu2中grade的值也会发生改变。

2、通过重写clone()方法进行浅拷贝
  Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。需要注意:1)Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用,在重写时将访问权限修改为public。2)使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone()方法。

深拷贝的两种方式

1、通过重写clone方法来实现深拷贝
  与通过重写clone方法实现浅拷贝的基本思路一样,只需要为对象每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝。

2、通过对象序列化进行深拷贝
  虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。
  将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

import java.io.*;

public class DeepCopyBySerialization {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Grade grade = new Grade(79);
        Student stu1 = new Student("张笑笑",grade);
        //通过序列化方式实现深拷贝
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(stu1);
        oos.flush();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Student stu2 = (Student)ois.readObject();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
        System.out.println("===========================");
        //修改stu1中的各属性,看stu2有没有什么变化
        stu1.setName("John");
        grade.setGrade(100);
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
    }
}

class Grade implements Serializable {
    private int grade;

    public Grade() {
    }

    public Grade(int grade) {
        this.grade = grade;
    }

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }

    @Override
    public String toString() {
        return "grade=" + grade;
    }
}

class Student implements Serializable{
    private String name;
    private Grade grade;

    public Student() {
    }

    //其中一个参数是另一个类的对象
    public Student(String name, Grade grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

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

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    @Override
    public String toString() {
        return "name=" + name +"," + grade.getGrade();
    }
}

运行结果:

name=张笑笑,79
name=张笑笑,79
===========================
name=John,100
name=张笑笑,79

注意:如果某个属性被transient修饰,那么该属性就无法被拷贝