zoukankan      html  css  js  c++  java
  • Cloneable接口的作用与深度克隆与浅度克隆

    cloneable接口的作用

    cloneable其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。Object中clone方法:

        protected native Object clone() throws CloneNotSupportedException;

    这里有一个疑问,Object中的clone方法是一个空的方法,那么他是如何判断类是否实现了cloneable接口呢?

    原因在于这个方法中有一个native关键字修饰。

       native修饰的方法都是空的方法,但是这些方法都是有实现体的(这里也就间接说明了native关键字不能与abstract同时使用。因为abstract修饰的方法与java的接口中的方法类似,他显式的说明了修饰的方法,在当前是没有实现体的,abstract的方法的实现体都由子类重写),只不过native方法调用的实现体,都是非java代码编写的(例如:调用的是在jvm中编写的C的接口),每一个native方法在jvm中都有一个同名的实现体,native方法在逻辑上的判断都是由实现体实现的,另外这种native修饰的方法对返回类型,异常控制等都没有约束。 

       由此可见,这里判断是否实现cloneable接口,是在调用jvm中的实现体时进行判断的。

    深入理解深度克隆与浅度克隆

    首先,在java中创建对象的方式有四种:

            一种是new,通过new关键字在堆中为对象开辟空间,在执行new时,首先会看所要创建的对象的类型,知道了类型,才能知道需 要给这个对象分配多大的内存区域,分配内存后,调用对象的构造函数,填充对象中各个变量的值,将对象初始化,然后通过构造方法返回对象的地址;

          另一种是clone,clone也是首先分配内存,这里分配的内存与调用clone方法对象的内存相同,然后将源对象中各个变量的值,填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。

    另外还有输入输出流,反射构造对象等

    下面通过几个例子来解析下浅度克隆与深度克隆的区别:

    浅度克隆测试:

    首先定义一个学生类

     1 public class Student {
     2     private String name; //姓名
     3     private int age; //年龄
     4     private StringBuffer sex; //性别
     5 
     6     public String getName() {
     7         return name;
     8     }
     9 
    10     public void setName(String name) {
    11         this.name = name;
    12     }
    13 
    14     public int getAge() {
    15         return age;
    16     }
    17 
    18     public void setAge(int age) {
    19         this.age = age;
    20     }
    21 
    22     public StringBuffer getSex() {
    23         return sex;
    24     }
    25 
    26     public void setSex(StringBuffer sex) {
    27         this.sex = sex;
    28     }
    29 
    30     @Override
    31     public String toString() {
    32         return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    33     }
    34 }

    其次定义一个学校类,类中重写clone方法

     1 public class School implements Cloneable {
     2     private String schoolName; //学校名称
     3     private int stuNums; //学校人数
     4     private Student stu; //一个学生
     5 
     6     public String getSchoolName() {
     7         return schoolName;
     8     }
     9 
    10     public void setSchoolName(String schoolName) {
    11         this.schoolName = schoolName;
    12     }
    13 
    14     public int getStuNums() {
    15         return stuNums;
    16     }
    17 
    18     public void setStuNums(int stuNums) {
    19         this.stuNums = stuNums;
    20     }
    21 
    22     public Student getStu() {
    23         return stu;
    24     }
    25 
    26     public void setStu(Student stu) {
    27         this.stu = stu;
    28     }
    29 
    30     @Override
    31     protected School clone() throws CloneNotSupportedException {
    32     // TODO Auto-generated method stub
    33         return (School) super.clone();
    34 35     }
    36 
    37     @Override
    38     public String toString() {
    39         return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";
    40     }
    41 
    42 }

    最后定义一个main类来测试一下:

     1    public static void main(String[] args) throws CloneNotSupportedException {
     2         School s1 = new School();
     3         s1.setSchoolName("实验小学");
     4         s1.setStuNums(100);
     5         Student stu1 = new Student();
     6         stu1.setAge(20);
     7         stu1.setName("zhangsan");
     8         stu1.setSex(new StringBuffer("男"));
     9         s1.setStu(stu1);
    10         System.out.println("s1: " + s1 + " s1的hashcode:" + s1.hashCode() + " s1中stu1的hashcode:" + s1.getStu().hashCode());
    11         School s2 = s1.clone(); //调用重写的clone方法,clone出一个新的school---s2
    12         System.out.println("s2: " + s2 + " s2的hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());
    13     }

    测试结果:

     

         可以看出s1与s2的hashcode不同,也就是说clone方法并不是把s1的引用赋予s2,而是在堆中重新开辟了一块空间,将s1复制过去,将新的地址返回给s2。   

          但是s1中stu的hashcode与s2中stu的hashcode相同,也就是这两个指向了同一个对象,修改s2中的stu会造成s1中stu数据的改变。但是修改s2中的基本数据类型与Stirng类型时,不会造成s1中数据的改变,基本数据类型例如int,在clone的时候会重新开辟一个四个字节的大小的空间,将其赋值。而String则由于String变量的唯一性,如果在s2中改变了String类型的值,则会生成一个新的String对象,对之前的没有影响。  这就是浅度克隆。

    如何实现深度clone?(下面时第一种方法,另外使用序列化将student变成流,输入再输出也可以)

    首先需要让student重写clone方法,实现cloneable接口

     1 public class Student implements Cloneable {
     2 
     3     private String name;
     4     private int age;
     5     private StringBuffer sex;
     6 
     7     public String getName() {
     8         return name;
     9     }
    10 
    11     public void setName(String name) {
    12         this.name = name;
    13     }
    14 
    15     public int getAge() {
    16         return age;
    17     }
    18 
    19     public void setAge(int age) {
    20         this.age = age;
    21     }
    22 
    23     public StringBuffer getSex() {
    24         return sex;
    25     }
    26 
    27     public void setSex(StringBuffer sex) {
    28         this.sex = sex;
    29     }
    30 
    31     @Override
    32     public String toString() {
    33         return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    34     }
    35 
    36     @Override
    37     protected Student clone() throws CloneNotSupportedException {
    38 // TODO Auto-generated method stub
    39         return (Student) super.clone();
    40     }
    41 }

    然后,在school的clone方法中将school中的stu对象手动clone一下。

    1 @Override
    2 protected School clone()throws CloneNotSupportedException{
    3 // TODO Auto-generated method stub
    4         School s=null;
    5         s=(School)super.clone();
    6         s.stu=stu.clone();
    7         return s;
    8 }

    再次执行main方法查看结果:

     1 public class Main {
     2     public static void main(String[] args) throws CloneNotSupportedException {
     3         School s1 = new School();
     4         s1.setSchoolName("实验小学");
     5         s1.setStuNums(100);
     6         Student stu1 = new Student();
     7         stu1.setAge(20);
     8         stu1.setName("zhangsan");
     9         stu1.setSex(new StringBuffer("男"));
    10         s1.setStu(stu1);
    11         System.out.println("s1: " + s1 + " s1的hashcode:" + s1.hashCode() + " s1中stu1的hashcode:" + s1.getStu().hashCode());
    12         School s2 = s1.clone(); //调用重写的clone方法,clone出一个新的school---s2
    13         System.out.println("s2: " + s2 + " s2的hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());
    14 
    15         //修改s2中的值,看看是否会对s1中的值造成影响
    16         s2.setSchoolName("希望小学");
    17         s2.setStuNums(200);
    18         Student stu2 = s2.getStu();
    19         stu2.setAge(30);
    20         stu2.setName("lisi");
    21         stu2.setSex(stu2.getSex().append("6666666"));
    22         s2.setStu(stu2);
    23 
    24         //再次打印两个school,查看结果
    25         System.out.println("-------------------------------------------------------------------------");
    26         System.out.println("s1: " + s1 + " hashcode:" + s1.hashCode() + " s1中stu1的hashcode:" + s1.getStu().hashCode());
    27         System.out.println("s2: " + s2 + " hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());
    28     }
    29 }

    打印结果:

     

    这里可以看到两个stu的hashcode已经不同了,说明这已经是两个对象了,但是在s2中修改sex的值,为什么还会影响到s1呢?

      原因在于sex的类型是Stringbuffer,在clone的时候将StringBuffer对象的地址传递了过去,而StringBuffer类型没有实现cloneable接口,也没有重写clone方法。

    这种情况应该怎么解决呢?

    1.只实现浅度clone

    2.stu2.setSex(new StringBuffer("newString"));  在设置stu2的sex时创建一个新的StringBuffer对象。

    转自:https://blog.csdn.net/qq_37113604/article/details/81168224

  • 相关阅读:
    小米手机无法连接eclipse调试解决方案
    黏性控件的使用
    快速索引 (对View的自定义)
    侧滑面板(对viewGroup的自定义)
    安卓程序员要拿到5000和1w的薪资,分别需要掌握哪些技术?
    1、话说linux内核
    嵌入式软件工程师面试
    arm汇编之 bne与beq
    不要做浮躁的嵌入式系统工程师
    内核里面writel(readl)是如何实现的
  • 原文地址:https://www.cnblogs.com/itplay/p/12090061.html
Copyright © 2011-2022 走看看