区分深拷贝和浅拷贝

来源:互联网 发布:瓶中船的制作材料淘宝 编辑:程序博客网 时间:2024/04/30 14:28

一、本文的主要内容

  1. 深拷贝和浅拷贝的定义和区别
  2. 利用Cloneable接口实现浅拷贝
  3. 利用Serializable接口实现深拷贝

二、深拷贝和浅拷贝的区别和定义

首先给出二者的定义:
1. 浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。
2. 深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。这个方式称为深拷贝。

三、利用Cloneable接口实现浅拷贝

java.lang包中的Cloneable接口中没有方法,通常实现此接口的类应该使用公共方法重写Object.clone()方法,实现对该类进行按字段复制。如果在没有实现Cloneable接口的实例上调用Object的clone方法,会抛出CloneNotSupportedException 异常。

clone()方法是Object类的方法,它是一个native的方法,所以在Object类中给出实现。

protected native Object clone() throws CloneNotSupportedException;
import java.io.IOException;/* * author: crystal * Date:2016-5-19 * 测试浅拷贝和深拷贝,这是一个实现浅拷贝的例子 */class Lesson1 implements Cloneable{    private String lessonNum;    private String lessonName;    public void setLessonNum(String num){        lessonNum = num;    }    public void setLessonName(String name){        lessonName = name;    }    public String getLessonNum(){        return lessonNum;    }    public String getLessonName(){        return lessonName ;    }}class Student1 implements Cloneable{    private int ID;    private String name;    private Lesson1 lesson;    Student1(int ID, String name){        this.ID = ID;        this.name = name;    }    Student1(){    }    Student1(int ID, String  name, Lesson1 lesson){        this(ID,name);        this.lesson = lesson;    }    public void setID(int ID){        this.ID = ID;    }    public void setName(String name){        this.name = name;    }    public void setLesson(Lesson1 le){        this.lesson = le;    }    public int getID(){        return ID;      }    public String getName(){        return name;    }    public Lesson1 getLesson(){        return lesson;    }    public Object clone() throws CloneNotSupportedException{        return super.clone();    }}public class CopyDemo1 {    public static void main(String[] args) throws ClassNotFoundException, IOException, CloneNotSupportedException{        Lesson1 le1 = new Lesson1();        le1.setLessonName("Math");        le1.setLessonNum("001");        Student1 std3 = new Student1(1,"jack",le1);        Student1 std4 = new Student1();        //浅拷贝        std4= (Student1)std3.clone();        System.out.println("学生3的学号:" + std3.getID() + ", 姓名:" + std3.getName() + "班级名:"+ std3.getLesson().getLessonName() + " 班级编号:" + std3.getLesson().getLessonNum());        System.out.println("学生4的学号:" + std4.getID() + ", 姓名:" + std4.getName() + "班级名:"+ std4.getLesson().getLessonName() + " 班级编号:" + std4.getLesson().getLessonNum());        std4.setID(2);         System.out.println("学生3的学号:" + std3.getID() + ", 姓名:" + std3.getName() + "班级名:"+ std3.getLesson().getLessonName() + " 班级编号:" + std3.getLesson().getLessonNum());        System.out.println("学生4的学号:" + std4.getID() + ", 姓名:" + std4.getName() + "班级名:"+ std4.getLesson().getLessonName() + " 班级编号:" + std4.getLesson().getLessonNum());        std4.setID(1);        std4.getLesson().setLessonNum("003");        std4.getLesson().setLessonName("Chinese");        System.out.println("学生3的学号:" + std3.getID() + ", 姓名:" + std3.getName() + "班级名:"+ std3.getLesson().getLessonName() + " 班级编号:" + std3.getLesson().getLessonNum());        System.out.println("学生4的学号:" + std4.getID() + ", 姓名:" + std4.getName() + "班级名:"+ std4.getLesson().getLessonName() + " 班级编号:" + std4.getLesson().getLessonNum());    }}

输出结果如下:

学生3的学号:1, 姓名:jack班级名:Math 班级编号:001学生4的学号:1, 姓名:jack班级名:Math 班级编号:001学生3的学号:1, 姓名:jack班级名:Math 班级编号:001学生4的学号:2, 姓名:jack班级名:Math 班级编号:001学生3的学号:1, 姓名:jack班级名:Chinese 班级编号:003学生4的学号:1, 姓名:jack班级名:Chinese 班级编号:003
  1. 前两行表明浅拷贝后可以输出相同的值。
  2. 中间的两行表明修改std4的基本类型成员变量(这里的学号和姓名)的值不会对std3造成影响,因为对于基本类型成员变量在浅拷贝的时候会给std4重新分配存储空间,彼此独立,修改不会互相影响。
  3. 最后两行表明,修改std4的引用型成员变量lesson1的时候,会对std3产生影响,可以知道std3和std4的lesson1指向同一个Lesson1实例。说明浅拷贝的对象和被拷贝的对象中的引用型成员变量指向同一个实例。那如果我们希望,对于引用型变量指向的实例也另外分配存储空间,保证其独立性,则要用到下面说到的深拷贝。

四、利用Serializable接口实现深拷贝

java.io包中Serializable接口用于开启序列化的功能。序列化接口没有方法或字段,仅用于标识可序列化的语义。关于序列化的概念可以看另外一篇讲序列化和反序列化的博客。
下面是例子:

import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;/* * author: crystal * Date:2016-5-19 * 测试浅拷贝和深拷贝,这是一个实现深拷贝的例子 */class Lesson2 implements Serializable{    private static final long serialVersionUID = -4165718646159112862L;    private String lessonNum;    private String lessonName;    public void setLessonNum(String num){        lessonNum = num;    }    public void setLessonName(String name){        lessonName = name;    }    public String getLessonNum(){        return lessonNum;    }    public String getLessonName(){        return lessonName ;    }}class Student2 implements Serializable{    /**     *      */    private static final long serialVersionUID = 4480092265931219628L;    private int ID;    private String name;    private Lesson2 lesson;    Student2(int ID, String name){        this.ID = ID;        this.name = name;    }    Student2(){    }    Student2(int ID, String  name, Lesson2 lesson){        this(ID,name);        this.lesson = lesson;    }    public void setID(int ID){        this.ID = ID;    }    public void setName(String name){        this.name = name;    }    public void setLesson(Lesson2 le){        this.lesson = le;    }    public int getID(){        return ID;      }    public String getName(){        return name;    }    public Lesson2 getLesson(){        return lesson;    }    public Object deepClone() throws IOException, ClassNotFoundException{        //序列化        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bos);        oos.writeObject(this);        //反序列化        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bis);        return ois.readObject();        }}public class CopyDemo2 {    public static void main(String[] args) throws ClassNotFoundException, IOException{        Lesson2 le2 = new Lesson2();        le2.setLessonName("Math");        le2.setLessonNum("001");        Student2 std3 = new Student2(1,"jack",le2);        Student2 std4 = new Student2();        std4= (Student2)std3.deepClone();        System.out.println("学生1的学号:" + std3.getID() + ", 姓名:" + std3.getName() + "班级名:"+ std3.getLesson().getLessonName() + " 班级编号:" + std3.getLesson().getLessonNum());        System.out.println("学生2的学号:" + std4.getID() + ", 姓名:" + std4.getName() + "班级名:"+ std4.getLesson().getLessonName() + " 班级编号:" + std4.getLesson().getLessonNum());        std4.getLesson().setLessonNum("002");        std4.getLesson().setLessonName("Chinese");        System.out.println("学生1的学号:" + std3.getID() + ", 姓名:" + std3.getName() + "班级名:"+ std3.getLesson().getLessonName() + " 班级编号:" + std3.getLesson().getLessonNum());        System.out.println("学生2的学号:" + std4.getID() + ", 姓名:" + std4.getName() + "班级名:"+ std4.getLesson().getLessonName() + " 班级编号:" + std4.getLesson().getLessonNum());    }}

输出的结果如下:

学生1的学号:1, 姓名:jack班级名:Math 班级编号:001学生2的学号:1, 姓名:jack班级名:Math 班级编号:001学生1的学号:1, 姓名:jack班级名:Math 班级编号:001学生2的学号:1, 姓名:jack班级名:Chinese 班级编号:002
  1. 前两行同样说明深拷贝得到相同的输出结果
  2. 后两行表明对于引用型成员变量,在深拷贝时会分配存储空间拷贝引用指向的实例,std3和std4中lesson2指向不同的地址空间。
0 0