在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的,要满足这种需求有很多途径。
克隆的实现方式
一、浅度克隆
浅度克隆对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于非基本数据类型的属性,仅仅复制一份引用给新产生的对象,即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。
浅度克隆步骤:
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后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
Java代码实例:
public class Product implements Cloneable {
private String name;
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
二、深度克隆
在浅度克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类,也实现克隆,这样对于非基本数据类型的属性,复制的不是一份引用,即新产生的对象和原始对象中的非基本数据类型的属性指向的不是同一个对象
深度克隆步骤:
要克隆的类和类中所有非基本数据类型的属性对应的类
1、都实现java.lang.Cloneable接口
2、都重写java.lang.Object.clone()方法
Java代码实例:
public class Attribute implements Cloneable {
private String no;
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
public class Product implements Cloneable {
private String name;
private Attribute attribute;
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
三、使用对象序列化和反序列化实现深度克隆
所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。
对象的序列化还有另一个容易被大家忽略的功能就是对象复制(Clone),Java中通过Clone机制可以复制大部分的对象,但是众所周知,Clone有深度Clone和浅度Clone,如果你的对象非常非常复杂,并且想实现深层 Clone,如果使用序列化,不会超过10行代码就可以解决。
虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。 你可以通过添加serialVersionUID属性来解决这个问题(在Java序列化与反序列化学习(二):序列化接口说明 的标题三中有此说明)。如果你的类是个单例(Singleton)类,虽然我们多线程下的并发问题来控制单例,但是,是否允许用户通过序列化机制或者克隆来复制该类,如果不允许你需要谨慎对待该类的实现(让此类不用实现Serializable接口或Externalizable接口和Cloneable接口就行了)。
Java代码实例:
public class Attribute {
private String no;
}
public class Product {
private String name;
private Attribute attribute;
public Product clone() {
ByteArrayOutputStream byteOut = null;
ObjectOutputStream objOut = null;
ByteArrayInputStream byteIn = null;
ObjectInputStream objIn = null;
try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
byteOut = new ByteArrayOutputStream();
objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(this);
// 将流序列化成对象
byteIn = new ByteArrayInputStream(byteOut.toByteArray());
objIn = new ObjectInputStream(byteIn);
return (ContretePrototype) objIn.readObject();
} catch (IOException e) {
throw new RuntimeException("Clone Object failed in IO.",e);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found.",e);
} finally{
try{
byteIn = null;
byteOut = null;
if(objOut != null) objOut.close();
if(objIn != null) objIn.close();
}catch(IOException e){
}
}
}
}
或者:
- public static <T extends Serializable> T copy(T input) {
- ByteArrayOutputStream baos = null;
- ObjectOutputStream oos = null;
- ByteArrayInputStream bis = null;
- ObjectInputStream ois = null;
- try {
- baos = new ByteArrayOutputStream();
- oos = new ObjectOutputStream(baos);
- oos.writeObject(input);
- oos.flush();
- byte[] bytes = baos.toByteArray();
- bis = new ByteArrayInputStream(bytes);
- ois = new ObjectInputStream(bis);
- Object result = ois.readObject();
- return (T) result;
- } catch (IOException e) {
- throw new IllegalArgumentException("Object can't be copied", e);
- } catch (ClassNotFoundException e) {
- throw new IllegalArgumentException("Unable to reconstruct serialized object due to invalid class definition", e);
- } finally {
- closeQuietly(oos);
- closeQuietly(baos);
- closeQuietly(bis);
- closeQuietly(ois);
- }
- }
#########################注意######################
Bean复制的几种框架中(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)都是相当于克隆中的浅克隆。
1)spring包和Apache中的 BeanUtils 采用反射实现
Spring: void copyProperties(Object source, Object target,String[] ignoreProperties)
Apache:void copyProperties(Object dest, Object orig)
2)cglib包中的 Beancopier 采用动态字节码实现
cglib: BeanCopier create(Class source, Class target,boolean useConverter)
例如:
BeanCopier copier =BeanCopier.create(stuSource.getClass(), stuTarget.getClass(), false);
copier.copy(stuSource, stuTarget, null);
公司内部对用到的bean属性复制做了下性能分析:
cglib BeanCopier 15ms
Spring BeanUtil 4031ms
apache BeanUtils 18514ms.
#########################注意######################
clone和BeanUtils.copyProperties对比:
Cloning is done by you. If the instance which you trying clone contains reference of another instance you have to write cloning code to that one too. What if the instances contains chain of references to other instances? So if you do cloning by yourself, there are chances that you might miss a small detail.
BeanUtils.copyProperties on otherhand take care of everything by itself. It reduces your effort.
相关推荐
Java提供了两种主要的克隆方式:浅度克隆(Shallow Clone)和深度克隆(Deep Clone)。理解这两种克隆的区别对于优化内存管理和复制复杂对象至关重要。 **浅度克隆(Shallow Clone)** 浅度克隆仅仅复制了对象本身...
在C#编程中,"深度复制"和"浅度复制"是对象复制过程中两个重要的概念,它们涉及到如何正确地复制复杂的数据结构。了解并熟练掌握这两种复制方式对于编写高效、可靠的代码至关重要。 首先,让我们来定义这两个术语:...
在C#编程中,了解深度复制和浅度复制的概念至关重要,因为它们直接影响到对象的复制行为,特别是当处理包含复杂数据结构的对象时。本文将详细解释这两种复制方式,并通过一个实例来展示它们的区别。 首先,让我们...
在进行对象克隆的过程中,根据复制的深度,可以分为浅度克隆和深度克隆。 浅度克隆(Shallow Clone)仅适用于对象的第一层属性,如果属性值为基本类型,则复制其值;如果属性值为引用类型,则复制其引用地址。这...
***中深度复制和浅度复制是编程中常见的概念,尤其是在涉及到对象复制和内存管理的场景中。为了理解这两个概念,首先我们需要明确值类型和引用类型的区别。 值类型直接存储其数据,而在.NET框架中包括了所有的基本...
深度学习与浅度学习相对,后者通常表现出机械记忆、僵化理解和生硬应用。 2. 初中数学深度学习的重要性:在初中数学教学中,深度学习有助于学生利用已有经验,通过理论梳理和经验提纯,使学习更加深入和有效。 3. ...
为了更好地理解深度学习的内涵,我们需明确深度学习与浅度学习的区别。深度学习者具备深度理解知识的能力,能够将新旧知识有机结合起来,并在新环境中灵活运用。而浅度学习者则主要依靠记忆应对考试,无法将知识转化...
深度学习与传统的浅度学习模式有显著不同,它强调在学生需求的基础上,通过高阶思维的学习方式,激发学生的学习潜力,并将新知识与学生已有的知识体系相结合,从而达到提升数学素养和综合能力的目的。本文将详细探讨...
与传统的浅度学习相比,深度学习更接近于实际的生物神经网络,这赋予了它更强大的能力,从而大幅提高车牌字符的识别率。 在深度学习神经网络中,卷积神经网络(CNN)特别适用于视觉图像处理领域。卷积层是CNN的核心...
Vue提供了一个非常实用的功能,即对数据变化的侦听机制,其中包括浅度监听和深度监听。此外,Vue还提供了一个特殊的选项——watch,用来侦听和响应Vue实例上的数据变动。 首先,我们来看浅度监听,它通常指的是Vue...
在“prototypeAndCreate.zip”压缩包中,我们可能找到与原型模式相关的代码示例,包括浅度克隆和深度克隆两种不同的复制策略。浅度克隆仅仅复制对象本身,而不复制其引用的对象,而深度克隆则会递归地复制对象及其...
浅度爬虫是相对于深度爬虫而言的,它主要集中在网站表面的页面,通常用于获取网页的元数据或者对特定领域的信息进行快速索引。本项目基于Java编程语言,为初学者提供了一个简单的入门实践,旨在帮助理解如何构建一个...
深度学习与传统的浅度学习不同,浅度学习更注重对知识的记忆和简单应用,而深度学习则包含了记忆、理解、应用、分析、评价和创造六个维度。深度学习的特点在于学习要求的主动性、知识体系的系统性和学习的持续性。 ...
网络语言浅度研究报告.doc
深度学习者能够把学习材料与已知知识相结合,提出见解和批判,而浅度学习者通常只是单纯地记忆知识以应对考试。 智慧课堂的概念起源于现代科学技术与教育技术的结合,目前世界范围内对智慧课堂的定义尚未达成共识。...
深度学习,这个概念最早由美国学者马蒂和赛尔乔于1976年提出,它是相对于浅度学习即仅仅是对知识的再现而言的。深度学习强调的是在现有知识的基础上,对新知识进行深刻理解并进行批判性的思考,进一步对知识进行创新...
浅度探索C++对象模型bin3.ppt