JAVA深复制(深克隆)与浅复制(浅克隆)
1.浅复制与深复制概念
⑴浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不
复制它所引用的对象。
1、为什么要重写clone()方法?
答案:Java中的浅度复制是不会把要复制的那个对象的引用对象重新开辟一个新的引用空间,当我们需要深度复制的时候,这个时候我们就要重写clone()方法。
2、为什么要重载equal方法?
答案:因为Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,否则不相等;如果你现在需要利用对象里面的值来判断是否相等,则重载equal方法。
⑵深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原
有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
2.Java的clone()方法
⑴clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
⑵Java中对象的克隆
①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。
④在派生类中实现Cloneable接口。
请看如下代码:
class Student implements Cloneable
{
String name;
int age;
Student(String name,int age)
{
this.name=name;
this.age=age;
}
public Object clone()
{
Object o=null;
try
{
o=(Student)super.clone();//Object 中的clone()识别出你要复制的是哪一个对象。
}
catch(CloneNotSupportedException e)
{
System.out.println(e.toString());
}
return o;
}
}
public static void main(String[] args)
{
Student s1=new Student("zhangsan",18);
Student s2=(Student)s1.clone();
s2.name="lisi";
s2.age=20;
System.out.println("name="+s1.name+","+"age="+s1.age);//修改学生2后,不影响学生1的值。
}
说明:
①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
②继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明之。
class Professor
{
String name;
int age;
Professor(String name,int age)
{
this.name=name;
this.age=age;
}
}
class Student implements Cloneable
{
String name;// 常量对象。
int age;
Professor p;// 学生1和学生2的引用值都是一样的。
Student(String name,int age,Professor p)
{
this.name=name;
this.age=age;
this.p=p;
}
public Object clone()
{
Student o=null;
try
{
o=(Student)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println(e.toString());
}
//o.p=(Professor)p.clone();
return o;
}
}
public static void main(String[] args)
{
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18,p);
Student s2=(Student)s1.clone();
s2.p.name="lisi";
s2.p.age=30;
System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//学生1的教授成为lisi,age为30。
}
那应该如何实现深层次的克隆,即修改s2的教授不会影响s1的教授?代码改进如下。
改进使学生1的Professor不改变(深层次的克隆)
class Professor implements Cloneable
{
String name;
int age;
Professor(String name,int age)
{
this.name=name;
this.age=age;
}
public Object clone()
{
Object o=null;
try
{
o=super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println(e.toString());
}
return o;
}
}
class Student implements Cloneable
{
String name;
int age;
Professor p;
Student(String name,int age,Professor p)
{
this.name=name;
this.age=age;
this.p=p;
}
public Object clone()
{
Student o=null;
try
{
o=(Student)super.clone();
}
catch(CloneNotSupportedException e)
{
System.out.println(e.toString());
}
o.p=(Professor)p.clone();
return o;
}
}
public static void main(String[] args)
{
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18,p);
Student s2=(Student)s1.clone();
s2.p.name="lisi";
s2.p.age=30;
System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//学生1的教授不 改变。
}
3.利用串行化来做深复制
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。
应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
如下为深复制源代码。
public Object deepClone()
{
//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。
class Teacher implements Serializable{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
}
class Student implements Serializable{
String name;//常量对象
int age;
Teacher t;//学生1和学生2的引用值都是一样的。
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.p=p;
}
public Object deepClone() throws IOException,
OptionalDataException,ClassNotFoundException{//将对象写到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);//从流里读出来
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.deepClone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);//学生1的老师不改变
}
示例2:--------------------------------------
package com.test; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; public class StudentClone implements Cloneable,Serializable
{
static final long serialVersionUID = -8084210473720589252L;
String name;
int age; List<String> list = new ArrayList<String>(){{
add("A");
add("B");
add("C");
}}; StudentClone(String name,int age)
{
this.name=name;
this.age=age;
} public Object clone(){ try{
//save the object to a byte array
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(this);
out.close(); //read a clone of the object from byte array
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
Object ret = in.readObject();
in.close(); return ret;
}catch(Exception e){
return null;
}
} public static void main(String[] args)
{
StudentClone s1= new StudentClone("zhangsan",50);
StudentClone s2=(StudentClone)s1.clone();
s2.list.add("D"); s2.name="lisi";
s2.age=20;
System.out.println("name="+s1.name+","+"age="+s1.age);//修改学生2后,不影响学生1的值。
System.out.println("name="+s2.name+","+"age="+s2.age);
System.out.println("name="+s1.name+","+"age="+s1.age); System.out.println(s1.list.size()+"hhhhhhh");
System.out.println(s2.list.size()+"uuuuuuu");
} }
java对象 深度克隆(不实现Cloneable接口)和浅度克隆
为什么需要克隆:
在实际编程过程中,我们常常要遇到这种情况:有一个对象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后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
- 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()方法
- 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属性来解决这个问题。如果你的类是个单例(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。
- 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 {
- byteOut = new ByteArrayOutputStream();
- objOut = new ObjectOutputStream(byteOut);
- objOut.writeObject(prototype);
- 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){
- }
- }
- }
- }
JAVA深复制(深克隆)与浅复制(浅克隆)的更多相关文章
- Java中的深拷贝(深复制)和浅拷贝(浅复制)
深拷贝(深复制)和浅拷贝(浅复制)是两个比较通用的概念,尤其在C++语言中,若不弄懂,则会在delete的时候出问题,但是我们在这幸好用的是Java.虽然java自动管理对象的回收,但对于深拷贝(深复 ...
- iOS-深复制(mutableCopy)与浅复制(copy)
浅复制:只复制指向对象的指针,而不复制引用对象本身.对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只是一个指针,对象本身资源还是只有一份(对象引用计数+1),那如果我们对A_copy执 ...
- clone()方法、深复制和浅复制
clone方法 Java中没有明确提供指针的概念和用法,而实质上没个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已. 在实际编程中,经常会遇到从某个已 ...
- js中的深复制与浅复制
前言 所谓深复制与浅复制(深拷贝与浅拷贝),乍一听感觉听高大上,像是一个非常难理解的概念,其实我们平常项目开发都是在用的,只是你可能不知道该怎么叫它的名字而已,就像你听熟了一首歌,就是不知道这首歌叫什 ...
- iOS 浅复制和深复制的深层理解,含示例
转载:https://www.zybuluo.com/MicroCai/note/50592 版权归 @MicroCai 所有 以下是正文: 浅复制就是指针拷贝:深复制就是内容拷贝. 集合的浅复制 ( ...
- iOS 集合的深复制与浅复制
概念 对象拷贝有两种方式:浅复制和深复制.顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针:深复制是直接拷贝整个对象内存到另一块内存中. 一图以蔽之 再简单些说:浅复制就是指针拷贝:深复制 ...
- C#中的深复制与浅复制
C#中分为值类型和引用类型,值类型的变量直接包含其数据,而引用类型的变量则存储对象的引用. 对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响到另一个变量.如 class Progra ...
- C#深复制与浅复制
C#深复制与浅复制 C#中对于数据的复制机制虽然简单但是容易让人误解.C#数据类型大体分为值类型(value type)与引用类型(reference type).对于值类型数据,复制的时候直接将数据 ...
- 也来谈一谈js的浅复制和深复制
1.浅复制VS深复制 本文中的复制也可以称为拷贝,在本文中认为复制和拷贝是相同的意思.另外,本文只讨论js中复杂数据类型的复制问题(Object,Array等),不讨论基本数据类型(null,unde ...
随机推荐
- POJ 1740
#include <iostream> #define MAXN 100 using namespace std; int _m[MAXN]; bool mark[MAXN]; int m ...
- POJ2676Sudoku
http://poj.org/problem?id=2676 题意 : 这个是我最喜欢玩的数独了,就是一个9乘9的宫格,填上1到9九个数字,每行每列每个宫格之内不能有重复的数字,给出的九宫格中,0是待 ...
- Oracle 6 - 锁
Oracle锁没有额外的开销?Oracle的锁是怎么实现的?因为其他数据库,锁都是一种稀有资源和开销. 答:代码级实现?? 没有锁的话,并发更新就会有丢失更新的问题. 悲观锁和乐观锁 悲观锁一般用于有 ...
- ASP.NET 免费开源控件
AspNetPager分页控件(当前版本:7.5.1) AspNetPager分页控件是应用于ASP.NET WebForm网站或应用程序中的自定义分页控件,支持默认的回发(Postback)分页和U ...
- Mac 下 docker安装
http://www.th7.cn/system/mac/201405/56653.shtml Mac 下 docker安装 以及 处理错误Cannot connect to the Docker d ...
- Android:实现数组之间的复制
System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制 System.arraycopy(src, srcPos, dst, dstPos, length); src ...
- 如何将phantomjs单独部署在服务端
如何将phantomjs单独部署在服务端 文章目录 一. 容我分析(lao dao)几句 二. 服务端 Look here 服务端phantomjs搭建 web端搭建及如何调用phantomjs 三. ...
- Android AIDL-跨进程
Android在设计理念上强调组件化,组件之间的依赖性很小.我们往往发一个Intent请求就可以启动另一个应用的Activity,或者一个你不知道在哪个进程的Service,或者可以注册一个广播,只要 ...
- node解析查询字符串
var http=require("http"); var url=require("url"); var pages=[ {id:"1", ...
- 点(Dot)与像素(Pixel)的区别
DPI中的点(Dot)与图像分辨率中的像素(Pixel)是容易混淆的两个概念, DPI中的点可以说是硬件设备最小的显示单元, 而像素则既可是一个点,又可是多个点的集合.在扫描仪扫描图像时,扫描仪的每一 ...