java 对象克隆

来源:互联网 发布:最新网络流行名词 编辑:程序博客网 时间:2024/06/07 21:46
  1. 为什么需要克隆:
    在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的,要满足这种需求有很多途径

  2. 克隆的实现方式
    2.1. 浅度克隆,使用clone
    对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于非基本数据类型的属性,仅仅复制一份引用给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。
    1、实现java.lang.Cloneable接口
    要clone的类为什么还要实现Cloneable接口呢?Cloneable接口是一个标识接口,不包含任何方法的!这个标识仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的 clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException异常。
    2、重载java.lang.Object.clone()方法
    JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

    观察一下Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。Object类中的clone()还是一个protected属性的方法,重载之后要把clone()方法的属性设置为public。

    Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

    class Teacher {    public int age;    public String name;    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }    public String getName(){        return name;    }    public void setName(String name){        this.name = name;    }    @Override    public String toString() {        return "Teacher [age= " + age + " , name= " + name + "]";    }}class Student implements Cloneable{    public int age;    public String name;    public Teacher teacher;    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }    public String getName(){        return name;    }    public void setName(String name){        this.name = name;    }    public Teacher getTeacher(){        return teacher;    }    public void setTeacher(Teacher teacher){        this.teacher = teacher;    }    @Override    public Object clone() throws CloneNotSupportedException {        return super.clone();    }    @Override    public String toString() {        return "Student [age= " + age + " , name= " + name + " , teacher= " + teacher + "]";    }}public class CloneTest{    public static void main(String[] args) throws Exception{        // teacher对象将被clone出来的Student对象共享.        Teacher teacher = new Teacher();        teacher.setAge(40);        teacher.setName("Teacher zhang");        Student student1 = new Student();        student1.setAge(20);        student1.setName("zhangsan");        student1.setTeacher(teacher);        // 复制出来一个对象student2        Student student2 = (Student) student1.clone();        System.out.println(student2);        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");        System.out.println(student1.getTeacher());        // 修改student2的引用对象        student2.getTeacher().setAge(50);        student2.getTeacher().setName("Teacher Li");        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");        System.out.println(student1.getTeacher());    }}

    输出结果为

    Student [age= 20 , name= zhangsan , teacher= Teacher [age= 40 , name= Teacher zhang]]~~~~~~~~~~~~~~~~~~~~~~Student [age= 20 , name= zhangsan , teacher= Teacher [age= 40 , name= Teacher zhang]]~~~~~~~~~~~~~~~~~~~~~~Student [age= 20 , name= zhangsan , teacher= Teacher [age= 50 , name= Teacher Li]]

    2.2. 深度克隆,使用clone
    在浅度克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类,也实现克隆,这样对于非基本数据类型的属性,复制的不是一份引用,即新产生的对象和原始对象中的非基本数据类型的属性指向的不是同一个对象。

    要克隆的类和类中所有非基本数据类型的属性对应的类
    1、都实现java.lang.Cloneable接口
    2、都重载java.lang.Object.clone()方法

    class Teacher implements Cloneable{    public int age;    public String name;    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }    public String getName(){        return name;    }    public void setName(String name){        this.name = name;    }    @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }    @Override    public String toString() {        return "Teacher [age= " + age + " , name= " + name + "]";    }}class Student implements Cloneable{    public int age;    public String name;    public Teacher teacher;    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }    public String getName(){        return name;    }    public void setName(String name){        this.name = name;    }    public Teacher getTeacher(){        return teacher;    }    public void setTeacher(Teacher teacher){        this.teacher = teacher;    }    @Override    public Object clone() throws CloneNotSupportedException {        Student student = (Student) super.clone();          // 将引用的对象teacher也clone下          student.setTeacher((Teacher) (student.getTeacher().clone()));          return student;      }    @Override    public String toString() {        return "Student [age= " + age + " , name= " + name + " , teacher= " + teacher + "]";    }}public class CloneTest{    public static void main(String[] args) throws Exception{        // teacher对象将不被clone出来的Student对象共享.        Teacher teacher = new Teacher();        teacher.setAge(40);        teacher.setName("Teacher zhang");        Student student1 = new Student();        student1.setAge(20);        student1.setName("zhangsan");        student1.setTeacher(teacher);        // 复制出来一个对象student2        Student student2 = (Student) student1.clone();        System.out.println(student2);        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");        System.out.println(student1);        // 修改student2的引用对象        student2.getTeacher().setAge(50);        student2.getTeacher().setName("Teacher Li");        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");        System.out.println(student1);    }}

    输出结果为

    Student [age= 20 , name= zhangsan , teacher= Teacher [age= 40 , name= Teacher zhang]]~~~~~~~~~~~~~~~~~~~~~~Student [age= 20 , name= zhangsan , teacher= Teacher [age= 40 , name= Teacher zhang]]~~~~~~~~~~~~~~~~~~~~~~Student [age= 20 , name= zhangsan , teacher= Teacher [age= 40 , name= Teacher zhang]]*/

    2.3.深度克隆,使用对象序列化和反序列化实现
    所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。

    对象的序列化还有另一个容易被大家忽略的功能就是对象复制(Clone),Java中通过Clone机制可以复制大部分的对象,但是众所周知,Clone有深度Clone和浅度Clone,如果你的对象非常非常复杂,并且想实现深层 Clone,如果使用序列化,不会超过10行代码就可以解决。

    虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。 你可以通过添加serialVersionUID属性来解决这个问题。如果你的类是个单例(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。

    先实现一个工具类:

    import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class CloneUtil {    private CloneUtil() {        throw new AssertionError();    }    public static <T> T clone(T obj) throws Exception {        // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝        ByteArrayOutputStream bout = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bout);        oos.writeObject(obj);        // 将流序列化成对象        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bin);        return (T) ois.readObject();        // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放    }}

    以下是测试代码:

    import java.io.Serializable;//利用序列化来做深复制//深度cloneclass Teacher implements Serializable{    private static final long serialVersionUID = -8834559347461591191L;    public int age;    public String name;    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }    public String getName(){        return name;    }    public void setName(String name){        this.name = name;    }    @Override    public String toString() {        return "Teacher [age= " + age + " , name= " + name + "]";    }}class Student implements Serializable{    // serialVersionUID    // 如果你的对象序列化后存到硬盘上面后,可是后来你却更改了类的field(增加或减少或改名),当你反序列化时,就会出现Exception的,这样就会造成不兼容性的问题。    // 但当serialVersionUID相同时,它就会将不一样的field以type的缺省值赋值(如int型的是0,String型的是null等),这个可以避开不兼容性的问题。所以最好给serialVersionUID赋值    private static final long serialVersionUID = 7991552226614088458L;    public int age;    public String name;    public Teacher teacher;    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }    public String getName(){        return name;    }    public void setName(String name){        this.name = name;    }    public Teacher getTeacher(){        return teacher;    }    public void setTeacher(Teacher teacher){        this.teacher = teacher;    }    @Override    public String toString() {        return "Student [age= " + age + " , name= " + name + " , teacher= " + teacher + "]";    }}public class CloneTest{    public static void main(String[] args) throws Exception{        // teacher对象将不被clone出来的Student对象共享.        Teacher teacher = new Teacher();        teacher.setAge(40);        teacher.setName("Teacher zhang");        Student student1 = new Student();        student1.setAge(20);        student1.setName("zhangsan");        student1.setTeacher(teacher);        // 复制出来一个对象student2        Student student2 = CloneUtil.clone(student1);        System.out.println(student2);        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");        System.out.println(student1);        // 修改student2的引用对象        student2.getTeacher().setAge(50);        student2.getTeacher().setName("Teacher Li");        System.out.println("~~~~~~~~~~~~~~~~~~~~~~");        System.out.println(student1);    }}

    注:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

  3. java 方法参数的理解
    方法参数 可理解为: 对于输入的实参 进行了一份拷贝, (1) 若方法参数为基本类型,则在栈内存中开辟新的空间,所有的方法体内部的操作都是针对这个拷贝的操作,并不会影响原来输入实参的值 (2)若方法参数为引用类型,该拷贝与输入实参指向了同一个对象,方法体内部对于对象的操作,都是针对的同一个对象。
    另外,除了在函数传值的时候是”引用传递”,在任何用”=”向对象变量赋值的时候都是”引用传递”。

    举例说明:

    import java.util.*;public class HashtableAdd {    public static void main(String[] args) {        Hashtable<String, StringBuffer> ht = new Hashtable<String, StringBuffer>();        StringBuffer sb = new StringBuffer();        sb.append("abc,");        ht.put("1", sb);        sb.append("def,");        ht.put("2", sb);        sb.append("mno,");        ht.put("3", sb);        sb.append("xyz.");        ht.put("4", sb);        int numObj = 0;        Enumeration it = ht.elements();        while (it.hasMoreElements()) {            System.out.print("get StringBufffer " + (++numObj)                    + " from Hashtable: ");            System.out.println(it.nextElement());        }    }}

    输出结果:

    get StringBufffer 1 from Hashtable: abc,def,mno,xyz.get StringBufffer 2 from Hashtable: abc,def,mno,xyz.get StringBufffer 3 from Hashtable: abc,def,mno,xyz.get StringBufffer 4 from Hashtable: abc,def,mno,xyz.

    分析:向Hashtable传递 StringBuffer对象是只传递了这个StringBuffer对象的引用!每一次向Hashtable表中put一次 StringBuffer,并没有生成新的StringBuffer对象,只是在Hashtable表中又放入了一个指向同一StringBuffer对象的引用而已。对Hashtable表存储的任何一个StringBuffer对象(更确切的说应该是对象的引用)的改动,实际上都是对同一个 “StringBuffer”的改动。所以Hashtable并不能真正存储能对象,而只能存储对象的引用。也应该知道这条原则对与Hashtable相似的Vector, List, Map, Set等都是一样的。

  4. clone中String与StringBuffer区别
    Object类中clone()方法产生的过程是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,若不使用深克隆,即不对引用类型的域进行克隆,会导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

    不是所有的类都能实现深度clone的,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个 final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是 SringBuffer对象,而且变量名仍是unCA):

    o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();

    下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在 StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和 StringBuffer类型变量用相应的方法改动之后打印结果:

    class CloneC implements Cloneable {    public String str;    public StringBuffer strBuff;    public Object clone() {        CloneC o = null;        try {            o = (CloneC) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return o;    }}public class CloneTest {    public static void main(String[] a) {        CloneC c1 = new CloneC();        c1.str = new String("initializeStr");        c1.strBuff = new StringBuffer("initializeStrBuff");        System.out.println("before clone,c1.str = " + c1.str);        System.out.println("before clone,c1.strBuff = " + c1.strBuff);        CloneC c2 = (CloneC) c1.clone();        c2.str = c2.str.substring(0, 5);        c2.strBuff = c2.strBuff.append(" change strBuff clone");        System.out.println("=================================");        System.out.println("after clone,c1.str = " + c1.str);        System.out.println("after clone,c1.strBuff = " + c1.strBuff);        System.out.println("=================================");        System.out.println("after clone,c2.str = " + c2.str);        System.out.println("after clone,c2.strBuff = " + c2.strBuff);    }}

    运行结果

    before clone,c1.str = initializeStrbefore clone,c1.strBuff = initializeStrBuff=================================after clone,c1.str = initializeStrafter clone,c1.strBuff = initializeStrBuff change strBuff clone=================================after clone,c2.str = initiafter clone,c2.strBuff = initializeStrBuff change strBuff clone

    打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把 Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个 String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被 Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。

0 0
原创粉丝点击