分一下几点讨论:

  • 为什么要克隆?
  • 如何实现克隆
  • 浅克隆和深克隆
  • 解决多层克隆问题
  • 总结

一:为什么要克隆?

  大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?

  答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。

  提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。

  而通过clone方法赋值的对象跟原来的对象时同时独立存在的。

二:如何实现克隆 

  Object提供了一个对象拷贝的默认方法clone方法,但是该方法是有缺陷的,它提供了一种浅拷贝方式,也就是它并不会把对象所有属性全部拷贝一份,而是有选择性的拷贝,拷贝规则如下:

1、基本类型

  如果变量是基本类型,则拷贝其值,比如:int、float、long等。

2、String字符串

  这个比较特殊,拷贝的是地址,是个引用,但是在修改的时候,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,此处可以认为String是个基本类型。

3、对象

  如果变量时一个实例对象,则拷贝地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。这在Java中很疯狂,因为它突破了访问权限的定义,一个private修饰的变量,竟然可以被两个实例对象访问。

  Cloneable接口如下:如果调用Object的clone方法,没有实现Cloneable接口,则会抛出CloneNotSupportedException

三:浅克隆和深克隆

1、浅克隆

  在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

  简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

  

  上图中person是一个对象,浅拷贝会导致拷贝的对象和之前的对象持有相同的person对象的地址引用,所以修改操作会互相影响。

  在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。 

2、深克隆

  在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

  简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

  上图中的person对象,深拷贝可以达到拷贝之后的person对象是独立的,互不影响。

  在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

  (如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

  序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

扩展
  Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

分别举例两个浅拷贝和深拷贝的例子:

class Student implements Cloneable{
private int number; public int getNumber() {
return number;
} public void setNumber(int number) {
this.number = number;
} @Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber()); stu2.setNumber(54321); System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}

结果:

  学生1:12345

  学生2:12345

  学生1:12345

  学生2:54321

  如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

System.out.println(stu1 == stu2); // false  

  上面的复制被称为浅克隆。

  还有一种稍微复杂的深度复制:

  我们在学生类里再加一个Address类。

class Address  {
private String add; public String getAdd() {
return add;
} public void setAdd(String add) {
this.add = add;
} } class Student implements Cloneable{
private int number; private Address addr; public Address getAddr() {
return addr;
} public void setAddr(Address addr) {
this.addr = addr;
} public int getNumber() {
return number;
} public void setNumber(int number) {
this.number = number;
} @Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test { public static void main(String args[]) { Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}

结果:

  学生1:123,地址:杭州市

  学生2:123,地址:杭州市

乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。

addr.setAdd("西湖区");  

System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());

结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区

  这就奇怪了,怎么两个学生的地址都改变了?

  原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

  所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

class Address implements Cloneable {
private String add; public String getAdd() {
return add;
} public void setAdd(String add) {
this.add = add;
} @Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
} class Student implements Cloneable{
private int number; private Address addr; public Address getAddr() {
return addr;
} public void setAddr(Address addr) {
this.addr = addr;
} public int getNumber() {
return number;
} public void setNumber(int number) {
this.number = number;
} @Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度复制
return stu;
}
}
public class Test { public static void main(String args[]) { Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}

结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市

这样结果就符合我们的想法了。

四:解决多层克隆问题

  如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
public Inner inner;
 //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 
public Outer myclone() {
Outer outer = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
      // 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}

Inner也必须实现Serializable,否则无法序列化:

public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
public String name = ""; public Inner(String name) {
this.name = name;
} @Override
public String toString() {
return "Inner的name值为:" + name;
}
}

这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

总结

实现对象克隆有两种方式:

  1). 实现Cloneable接口并重写Object类中的clone()方法;

  2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

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

java 浅拷贝和深拷贝 对象克隆clone的更多相关文章

  1. Java 浅拷贝、深拷贝,你知多少?

    这是今天我们在技术群里面讨论的一个知识点,讨论的相当激烈,由于对这一块使用的比较少,所以对这一块多少有些盲区.这篇文章总结了所讨论的内容,希望这篇文章对你有所帮助. 在 Java 开发中,对象拷贝或者 ...

  2. Java 浅拷贝和深拷贝的理解和实现方式

    Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.举例说明:比如,对象A和对象B都属于类S,具有属性a和b.那么对对象A进行拷贝 ...

  3. 浅谈java浅拷贝和深拷贝

    前言:深拷贝和浅拷贝的区别是什么? 浅拷贝:被复制的对象的所有变量都含有原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之, 浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.深拷 ...

  4. 【转】Java 浅拷贝和深拷贝的理解和实现方式

    Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去.举例说明:比如,对象A和对象B都属于类S,具有属性a和b.那么对对象A进行拷贝 ...

  5. java浅拷贝和深拷贝

    转:http://blog.csdn.net/u014727260/article/details/55003402 实现clone的2点: 1,clone方法是Object类的一个方法,所以任何一个 ...

  6. 面试题常考&必考之--js中的对象的浅拷贝和深拷贝(克隆,复制)(下)

    这里主要是讲深拷贝: 深拷贝:个人理解就是拷贝所有的层级 1.像对象里再放数组和对象这些叫引用值.开始我们先判断大对象中是否有引用值(数组和小对象), 然后在判断引用值是数组还是对象 2.开始啦: 1 ...

  7. Java 浅拷贝和深拷贝

    一看就懂的,java深拷贝浅拷贝 将一个对象的引用复制给另外一个对象,一共有三种方式.第一种方式是直接赋值,第二种方式是浅拷贝,第三种是深拷贝.所以大家知道了哈,这三种概念实际上都是为了拷贝对象啊. ...

  8. Java 浅拷贝,深拷贝

         从Java 强引用.软引用,弱引用http://blog.csdn.net/jltxgcy/article/details/35558465一文中,我们看到把一个对象赋值给另一个对象,本质上 ...

  9. java浅拷贝和深拷贝(基础也是很重要的)

    对象的copy你兴许只是懵懂,或者是并没在意,来了解下吧. 对于的github基础代码https://github.com/chywx/JavaSE 最近学习c++,跟java很是相像,在慕课网学习c ...

随机推荐

  1. MYSQL基本操作(上)

    很久之前,就想做个Mysql的小结,毕竟数据库知识是软件研发的基本技能,这里话不多说,开始总结一波. 数据库基本概念 数据库为高效的存储和处理数据的介质(主要分为磁盘和内存两种),一般关系型数据库存储 ...

  2. Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用

    从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...

  3. ELK+Kafka 企业日志收集平台(一)

    背景: 最近线上上了ELK,但是只用了一台Redis在中间作为消息队列,以减轻前端es集群的压力,Redis的集群解决方案暂时没有接触过,并且Redis作为消息队列并不是它的强项:所以最近将Redis ...

  4. 在 PHP 7 中不要做的 10 件事

    在 PHP 7 中不要做的 10 件事 1. 不要使用 mysql_ 函数 这一天终于来了,从此你不仅仅“不应该”使用mysql_函数.PHP 7 已经把它们从核心中全部移除了,也就是说你需要迁移到好 ...

  5. C++ 星号* 与 引用&

    星号 * 1. 声明的时候有*, 表示指针变量 int *p=&a;// '&'的作用就是把a变量在内存中的地址给提取出来 2. * +地址, 表示地址操作符 3. 数字*数字, 表示 ...

  6. hive并行执行作业; 强化在脑海的印象

    如果集群资源充足可以设置:set hive.exec.parallel=true; (默认是false) 这样相互独立的job可以并行执行!!!! count(distinct) 最好改写为group ...

  7. 008_python列表的传值与传址

    一. 今天发现一个奇怪的现象,代码如下: aList = ['xyz', 'zara', 'abc', 'xyz','xysdfji','xywooudd'] for x in aList: if x ...

  8. 8.oop-多态

    一.继承1.定义:子类继承父类,会继承父类的属性和方法2.语法:extends关键字 子类 extends 父类3.特点:java中的继承是单一继承,子类只能继承一个父类,但是父类可以有多个子类4.用 ...

  9. robotframework连接mysql数据库

    1.安装databaselibrary.pymysql 通过cmd命令执行: pip install robotframework-databaselibrary pip install pymysq ...

  10. # 20175329 2018-2019-2 《Java程序设计》第二周学习总结

    # 学号 2018-2019-3<Java程序设计>第三周学习总结 ## 教材学习内容总结 第二三章与我们所学习的C语言有很多的相似点,在这里我想主要就以我所学习的效果来讨论一下JAVA与 ...