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方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。

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

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

浅度克隆测试:

首先定义一个学生类

 public class Student {
private String name; //姓名
private int age; //年龄
private StringBuffer sex; //性别 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 StringBuffer getSex() {
return sex;
} public void setSex(StringBuffer sex) {
this.sex = sex;
} @Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}

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

 public class School implements Cloneable {
private String schoolName; //学校名称
private int stuNums; //学校人数
private Student stu; //一个学生 public String getSchoolName() {
return schoolName;
} public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
} public int getStuNums() {
return stuNums;
} public void setStuNums(int stuNums) {
this.stuNums = stuNums;
} public Student getStu() {
return stu;
} public void setStu(Student stu) {
this.stu = stu;
} @Override
protected School clone() throws CloneNotSupportedException {
    // TODO Auto-generated method stub
return (School) super.clone();
} @Override
public String toString() {
return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";
} }

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

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

测试结果:

可以看出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接口

 public class Student implements Cloneable {

     private String name;
private int age;
private StringBuffer sex; 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 StringBuffer getSex() {
return sex;
} public void setSex(StringBuffer sex) {
this.sex = sex;
} @Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
} @Override
protected Student clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return (Student) super.clone();
}
}

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

 @Override
protected School clone()throws CloneNotSupportedException{
// TODO Auto-generated method stub
School s=null;
s=(School)super.clone();
s.stu=stu.clone();
return s;
}

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

 public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
School s1 = new School();
s1.setSchoolName("实验小学");
s1.setStuNums(100);
Student stu1 = new Student();
stu1.setAge(20);
stu1.setName("zhangsan");
stu1.setSex(new StringBuffer("男"));
s1.setStu(stu1);
System.out.println("s1: " + s1 + " s1的hashcode:" + s1.hashCode() + " s1中stu1的hashcode:" + s1.getStu().hashCode());
School s2 = s1.clone(); //调用重写的clone方法,clone出一个新的school---s2
System.out.println("s2: " + s2 + " s2的hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode()); //修改s2中的值,看看是否会对s1中的值造成影响
s2.setSchoolName("希望小学");
s2.setStuNums(200);
Student stu2 = s2.getStu();
stu2.setAge(30);
stu2.setName("lisi");
stu2.setSex(stu2.getSex().append("6666666"));
s2.setStu(stu2); //再次打印两个school,查看结果
System.out.println("-------------------------------------------------------------------------");
System.out.println("s1: " + s1 + " hashcode:" + s1.hashCode() + " s1中stu1的hashcode:" + s1.getStu().hashCode());
System.out.println("s2: " + s2 + " hashcode:" + s2.hashCode() + " s2中stu1的hashcode:" + s2.getStu().hashCode());
}
}

打印结果:

这里可以看到两个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

Cloneable接口的作用与深度克隆与浅度克隆的更多相关文章

  1. Java的深度克隆和浅度克隆

    说到克隆,其实是个比较简单的概念,跟现实生活正的克隆一样,复制一个一模一样的对象出来.clone()这个方法是从Object继承下来的,一个对象要实现克隆,需要实现一个叫做Cloneable的接口,这 ...

  2. Java中深度克隆和浅度克隆

    一:使用目的: 就是为了快速构造一个和已有对象相同的副本.如果需要克隆对象,一般需要先创建一个对象,然后将原对象中的数据导入到新创建的对象中去,而不用根据已有对象进行手动赋值操作. 二:Object中 ...

  3. Cloneable接口的作用

    Cloneable接口是一个[标记接口],就是没有任何内容 implements Cloneable表示该对象能被克隆,能使用Object.clone()方法.如果没有implements Clone ...

  4. java对象 深度克隆(不实现Cloneable接口)和浅度克隆

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt128 为什么需要克隆: 在实际编程过程中,我们常常要遇到这种情况:有一个对象 ...

  5. Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨

    Java对象克隆(Clone)及Cloneable接口.Serializable接口的深入探讨 Part I 没啥好说的,直接开始Part II吧. Part II 谈到了对象的克隆,就不得不说为什么 ...

  6. (转)Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨

    原文地址:http://blog.csdn.net/kenthong/article/details/5758884 Part I 没啥好说的,直接开始Part II吧. Part II 谈到了对象的 ...

  7. java 的对象拷贝(有深浅拷贝两种方式,深拷贝实现的两种方式(逐层实现cloneable接口,序列化的方式来实现))

    Java提高篇--对象克隆(复制)(转自:http://www.cnblogs.com/Qian123/p/5710533.html#_label0)   阅读目录 为什么要克隆? 如何实现克隆 浅克 ...

  8. 001 Java 深拷贝、浅拷贝及Cloneable接口

    原本写过,后来在阅读的时候,感觉自己都不是太明白了,删除后参考人家的又重新写了一份. 一:开篇 1.复制一个变量 举例是int类型. 其他其中七种原始数据类型同样适用. 原始类型:boolean,ch ...

  9. Cloneable接口和Object的clone()方法

    为什么要克隆 为什么要使用克隆,这其实反映的是一个很现实的问题,假如我们有一个对象: public class SimpleObject implements Cloneable { private ...

随机推荐

  1. 中国MOOC_面向对象程序设计——Java语言_第1周 类与对象_1分数

    第1周编程题 查看帮助 返回   我们在题目说明中给出了一部分代码,你需要在这部分代码的基础上,按照题目说明编写代码,然后将两部分代码一起提交. 依照学术诚信条款,我保证此作业是本人独立完成的. 温馨 ...

  2. 中国MOOC_零基础学Java语言_第5周 数组

    第5周 数组 5.1 数组 5.2 数组计算 public class Main { public static void main(String[] args) { for (int i = 1; ...

  3. Mybatis使用时 resultMap与resultType、parameterMap与 parameterType的区别

    Map:映射:Type:Java类型  resultMap 与 resultType.parameterMap 与  parameterType的区别在面试的时候被问到的几率非常高,出现的次数到了令人 ...

  4. LeetCode.860-卖柠檬水找零(Lemonade Change)

    这是悦乐书的第331次更新,第355篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第201题(顺位题号是860).在柠檬水摊上,每杯柠檬水的价格为5美元.客户站在队列中向 ...

  5. MySQL学习-入门语句以及增删查改

    1. SQL入门语句 SQL,指结构化查询语言,全称是 Structured Query Language,是一种 ANSI(American National Standards Institute ...

  6. 递归算法之不用乘号的乘法——用位移实现乘法(dart语言实现)

    前两天突发奇想,写一个乘法的实现,但不用乘号*.并测试一下性能如何.因此就有了下面的代码:(本文主要目的是为了玩递归和位移,因此仅限自然数) 首先,标准乘法: int commonMultiplica ...

  7. Linux 系统安装 python

    Centos 7 Centos 7 安装 python3 (不要卸载python2 因为yum 要用) https://phoenixnap.com/kb/how-to-install-python- ...

  8. MyEclipse img显示问题

    MyEclipse第一个例子,通过HTML的img显示图片,费两天的功夫,故作简单整理,图片2不显示根本原因还是src的路径不对. 选中project中的sr.jpg文件,运行查看浏览器显示的地址,此 ...

  9. 循环结构 :for

    循环结构 :for 循环四要素: 1.初始化条件 2.循环条件 3.循环体 4.迭代条件 格式: for(初始化条件;循环条件;迭代条件){ 循环体; } 执行顺序 :1 -> 2 -> ...

  10. 【WPS/Visio】WPS word无法复制或编辑Visio对象

    前言 Win10,WPS2019,Visio2016. 好像是有一次设置 .vsdx 的默认打开方式为Visio,之后每次在WPS里复制Visio对象,或双击编辑WPS word中以前的Visio对象 ...