java 浅克隆与深克隆

来源:互联网 发布:美国非农数据统计 编辑:程序博客网 时间:2024/06/15 11:28

克隆一般指的是复制一份一模一样的副本,java中这样的场合其实挺多。不过因为java自身的数据类型设计,java的克隆有了浅克隆和深克隆的两种克隆方式。

java的数据类型:

这里写图片描述

基本数据类型的复制

如上图所示,java中的数据分为简单数据类型和引用数据类型,简单数据类型都是值传递的。举个例子:

    @Test    public void Test() {        int i = 20;        int j = i;        i = 30;        System.out.println("i : " + i);   //i : 30        System.out.println("j :" + j);    //j :20    }   

简单引用数据类型的”=”复制

对于引用数据类型,传递的是对象的引用而不是对象本身,比如创建一个Student类:

public class Student {    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

使用” = “来“复制”对象:

    @Test    public void Test() {        Student stu1 = new Student();        stu1.setName("jack");        Student stu2 = stu1;        stu1.setName("tom");        System.out.println("stu1 : " + stu1.getName());//stu1 : tom        System.out.println("stu2 : " + stu2.getName());//stu2 : tom    }   

可以发现复制之后再修改stu1仍然会影响stu2,他们之间是共享的一个student对象实例。这样就比较麻烦了,因为我们一般是希望复制的对象是完全独立的副本,不会相互影响。

Object clone 方法

Object 类提供了一个clone方法,可以提供一个相同的副本。

 /**     * Creates and returns a copy of this object.  The precise meaning     * of "copy" may depend on the class of the object.      * x.clone() != x     will be true, and that the expression:     * x.clone().getClass() == x.getClass()          *            will be {@code true}, but these are not absolute requirements.     * x.clone().equals(x)    will be {@code true}, this is not an absolute requirement.      */    protected native Object clone() throws CloneNotSupportedException;

要特别注意的是,这个克隆方法是否完全克隆对象要取决于对象的类型,clone之后满足下面三点 :
- x.clone() != x ; 即克隆对象和原对象不是同一个
- x.clone().getClass() == x.getClass() ; 克隆的对象和被克隆对象类型相同
- x.clone().equals(x) ;克隆对象和被克隆对象内容相同,这里要特别注意,如果保存的是相同的对象的引用,那也算是相同的。

简单对象的clone

要使用clone方法,首先要实现Cloneable接口,这个接口本身并没有什么方法,是一个标志接口,表示可以克隆。
然后要复写clone方法。现在修改之前的student类

public class Student implements Cloneable{    private String name;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }//重写clone方法    public Student clone(){        Student stu = null;        try {            stu =(Student)super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return  stu;    }}

使用clone方法进行克隆测试。

    @Test    public void test() throws CloneNotSupportedException {        Student stu1 = new Student();        stu1.setName("jack");        Student stu2 =  stu1.clone();        stu1.setName("tom");        System.out.println("stu1 : " + stu1.getName());//stu1 : tom        System.out.println("stu2 : " + stu2.getName());//stu2 : jack    }

可见,这次修改stu1的name值之后并不会影响到stu2.但这个student的属性只是简单数据类型.

有属性是引用数据类型的对象clone

我们再创建一个School类,先假设一个学校只有一个学生,并且实现cloneable接口重写clone方法:

public class School implements Cloneable {    private String name;    private Student stu;    public School(String name, Student stu) {        this.name = name;        this.stu = stu;    } //getter and setter....    public School clone(){        School school = null;        try {            school = (School) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return  school;    }}

同样的进行学校的克隆测试。

    @Test    public void test() throws CloneNotSupportedException {        Student stu1 = new Student();        stu1.setName("jack");        School school1 = new School("小学1号",stu1);        School school2 = school1.clone();        school2.setName("小学2号");        school2.getStu().setName("tom");        System.out.println("school1 : " + school1.getName() + " , " +school1.getStu().getName());        //school1 : 小学1号 , tom        System.out.println("school2 : " + school2.getName() + " , " +school2.getStu().getName());        //school2 : 小学2号 , tom    }

如果是完全克隆的副本,那么我们修改school2的信息是不贵改掉school1的信息的,但是我们发现,school1的学生姓名也跟着改变了,这就说明不是完全的复制。

浅克隆

之前说过Object的clone方法,是根据克隆的类型来看的,我们来分析一下school的属性类型。

    private String name; //fianl 值传递    private Student stu; //对象 引用传递

从结果上来看,值传递的属性是直接复制的副本,但是对象属性student是直接传递的引用,因为clone太懒,不想去复制。这样的克隆方式为浅克隆。

这里写图片描述

深克隆

为了避免clone偷懒,我们只能手动的声明,对象属性也要全部复制,修改school的clone方法:

    public School clone(){        School school = null;        try {            school = (School) super.clone();            school.stu = this.stu.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return  school;    }

之后进行测试,结果为:

school1 : 小学1号 , jackschool2 : 小学2号 , tom

这次的操作便是完全的克隆了。也就是深克隆

这里写图片描述

集合的clone

我的理解是,集合也是对象,集合的元素也可以看成是对象的属性,那么也应该存在浅克隆和深克隆的问题。不过也要进行测试验证。用list进行测试好了。
为了避免写重复代码,先将list遍历的方法单独写出来。

    public void showList(List<String> list){        if (list.size() == 0){            System.out.println("no data");            return;        }        for(String s : list){            System.out.print(s + "\t");        }        System.out.println("");    }

在java中对集合的复制有很多方法,比如:

方法一 : 直接“=”复制。

   @Test    public void test() throws CloneNotSupportedException {        ArrayList<String> list1 = new ArrayList<>();        list1.add("no1");        list1.add("no2");        ArrayList<String> list2 = list1;        list1.add("No3");        showList(list1); //no1  no2 No3         showList(list2);//no1   no2 No3     }

可以看出来这种复制方法明显不靠谱,因为集合也是对象,使用”=”只是传递了引用,实例还是共享的。

方法二 : 使用构造函数

    @Test    public void test() throws CloneNotSupportedException {        ArrayList<String> list1 = new ArrayList<>();        list1.add("no1");        list1.add("no2");        ArrayList<String> list2 = new ArrayList<>(list1); //使用构造函数        list1.add("No3");        list1.set(0,"no4");        showList(list1);//no4   no2 No3         showList(list2);//no1   no2    }

使用构造函数的方法达到了克隆的目的,因为,修改list1并没有影响到list2.

方法三 : 遍历添加

   @Test    public void test() throws CloneNotSupportedException {        ArrayList<String> list1 = new ArrayList<>();        list1.add("no1");        list1.add("no2");        ArrayList<String> list2 = new ArrayList<>();        //遍历添加        for(String s : list1){            list2.add(s);        }        list1.set(0,"No3");        showList(list1);//No3   no2         showList(list2);//no1   no2    }

因为是遍历添加的,所以就是把list1的每一个元素拷贝到了list2中,再去改list1的元素,就不会影响到list2了。

方法四 : 使用工具类复制 Collections.copy

    @Test    public void test() throws CloneNotSupportedException {        ArrayList<String> list1 = new ArrayList<>();        list1.add("no1");        list1.add("no2");        ArrayList<String> list2 = new ArrayList(Arrays.asList(new String[list1.size()]));        Collections.copy(list2,list1);        list1.set(0,"No3");        showList(list1);//No3   no2         showList(list2);//no1   no2     }

这个方法本身就很不方便。

方法五 使用list自己的clone方法:

    public Object clone() {        try {            ArrayList<?> v = (ArrayList<?>) super.clone();            v.elementData = Arrays.copyOf(elementData, size);            v.modCount = 0;            return v;        } catch (CloneNotSupportedException e) {            // this shouldn't happen, since we are Cloneable            throw new InternalError(e);        }    }
  @Test    public void test() throws CloneNotSupportedException {        ArrayList<String> list1 = new ArrayList<>();        list1.add("no1");        list1.add("no2");        ArrayList<String> list2 = (ArrayList<String>)list1.clone();        Collections.copy(list2,list1);        list1.set(0,"No3");        showList(list1);//No3   no2        showList(list2);//no1   no2    }

后边四种方法都可以实现list集合的复制,并且修改原集合雷荣不影响其他集合,但是集合元素类型是String,下面将集合内容改为Student进行测试。

修改showlist方法:

    public void showList(List<Student> list){        if (list.size() == 0){            System.out.println("no data");            return;        }        for(Student s : list){            System.out.print(s.getName() + "\t");        }        System.out.println("");    }

第一种 :使用构造方法

 @Test    public void test() throws CloneNotSupportedException {        ArrayList<Student> list1 = new ArrayList<>();        Student stu1 = new Student();        stu1.setName("jack");        list1.add(stu1);        ArrayList<Student> list2 = new ArrayList<>(list1); //使用构造函数        list1.get(0).setName("tom");        showList(list1);//tom        showList(list2);//tom       }

第二种 使用集合遍历添加

    @Test    public void test() throws CloneNotSupportedException {        ArrayList<Student> list1 = new ArrayList<>();        Student stu1 = new Student();        stu1.setName("jack");        list1.add(stu1);        ArrayList<Student> list2 = new ArrayList<>();        for(Student t : list1){            list2.add(t);        }        list1.get(0).setName("tom");        showList(list1);//tom        showList(list2);//tom    }

第三种 使用集合类Collections.copy

    @Test    public void test() throws CloneNotSupportedException {        ArrayList<Student> list1 = new ArrayList<>();        Student stu1 = new Student();        stu1.setName("jack");        list1.add(stu1);        ArrayList<Student> list2 = new ArrayList(Arrays.asList(new Student[list1.size()]));        Collections.copy(list2,list1);        list1.get(0).setName("tom");        showList(list1);//tom        showList(list2);//tom    }

第四种 使用arrayList 的clone方法。

    @Test    public void test() throws CloneNotSupportedException {        ArrayList<Student> list1 = new ArrayList<>();        Student stu1 = new Student();        stu1.setName("jack");        list1.add(stu1);        ArrayList<Student> list2 = (ArrayList<Student>)list1.clone();        Collections.copy(list2,list1);        list1.get(0).setName("tom");        showList(list1);//tom        showList(list2);//tom    }

经过测试,以上四种方法全部壮烈牺牲。原因总结起来是一样的,因为student是对象类型,传递的是引用。这样的话,我们只能每个都clone之后再复制给新的list。

   @Test    public void test() throws CloneNotSupportedException {        ArrayList<Student> list1 = new ArrayList<>();        Student stu1 = new Student();        stu1.setName("jack");        list1.add(stu1);        ArrayList<Student> list2 = new ArrayList<>();        for(Student t : list1){            list2.add(t.clone()); //clone        }        list1.get(0).setName("tom");        showList(list1);//tom        showList(list2);//jack    }

这样就成功了,但是前提是Student类的clone方法是我们自己重写过的。
下面我们将school类的student属性改为arrayList.

import java.util.ArrayList;public class School implements Cloneable {    private String name;    private ArrayList<Student> students;    public School() {    }    public School(String name, ArrayList<Student> students) {        this.name = name;        this.students = students;    }   // getter and setter ...   public School clone(){        School s = null;        ArrayList<Student> list = new ArrayList<>();        try {            s = (School)super.clone(); //clone1            for(Student t : this.getStudents()){                list.add(t.clone()); //clone2            }            s.setStudents(list);        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return s;    }}

school克隆测试

import org.junit.Test;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;public class MyTest {    @Test    public void test() throws CloneNotSupportedException {        Student stu1 = new Student();        stu1.setName("jack");        Student stu2 = new Student();        stu2.setName("tom");        ArrayList<Student> students = new ArrayList<>();        students.add(stu1);        School school1 = new School("小学1号",students);        School school2 = school1.clone();        school2.setName("小学2号");        school2.getStudents().add(stu2);        school2.getStudents().get(0).setName("linda");        showSchool(school1);//小学1号  jack        showSchool(school2);//小学2号  linda tom       }    public void showSchool(School school){        System.out.print(school.getName() + "\t");        showList(school.getStudents());    }    public void showList(List<Student> list){        if (list.size() == 0){            System.out.println("no data");            return;        }        for(Student s : list){            System.out.print(s.getName() + "\t");        }        System.out.println("");    }}

这样就实现了school的深度克隆。

序列化克隆

school里边只有一个student 的list还不算很复杂,但如果有更多的集合,更多的引用类型属性,那再通过修改clone方法其实是很麻烦的一件事情。这时候可以通过序列化的方法来深度克隆。
这就需要对象实现Serializable 接口了,而且student类也要实现Serializable 接口。

import java.io.*;import java.util.ArrayList;public class School implements Serializable {    private String name;    private ArrayList<Student> students;    public School() {    }    public School(String name, ArrayList<Student> students) {        this.name = name;        this.students = students;    }//getter and setter ...    public School MyClone() throws IOException, ClassNotFoundException {        School s = null;        //创建字节输出流        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();        //对象输出流        ObjectOutputStream objOut = new ObjectOutputStream(byteOut);        objOut.writeObject(this);        //字节输入流        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());        //对象输入流        ObjectInputStream objIn = new ObjectInputStream(byteIn);        s =(School) objIn.readObject();        return s;    }}

测试能否实现深度克隆:

        showSchool(school1);//小学1号  jack        showSchool(school2);//小学2号  linda   tom 

可见,实现了深度克隆。