Java 深拷贝,浅拷贝
一直听说这两个词,确实不知道代表啥意思?也不知道究竟要用来做什么?什么时候用到他们。
下面是从一篇博文种得到的解释:
浅复制(浅克隆) :被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深复制(深克隆) :被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制。
有两种方式:(目前还不知道怎么做)
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆;
实现clone方法的步骤
(1)实现Cloneable接口
(2)重载Object类中的clone()方法,重载时需定义为public
(3)在重载方法中,调用super.clone()
package lesson1211; public class Student implements Cloneable { //不实现Cloneable接口,编译不会报错,但是运行时会报异常,所以必须实现Cloneable接口
private int number; //浅复制
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
protected Object clone(){ Student stu = null;
try {
stu = (Student)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stu;
} /**
* @return the number
*/
public int getNumber() {
return number;
} /**
* @param number the number to set
*/
public void setNumber(int number) {
this.number = number;
}
} package lesson1211; public class TestClone { public static void main(String[] args) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("stu1: " + stu1.getNumber());
System.out.println("stu2: " + stu2.getNumber()); stu2.setNumber(45678); System.out.println("stu1: " + stu1.getNumber());
System.out.println("stu2: " + stu2.getNumber());
}
}
解释:
(1)clone()方法是定义在java.lang.Object类中,该方法是一个protected的方法,所以重载时要把clone()方法的属性设置为public,这样其它类才能调用这个clone类的clone()方法
(2)实现Cloneable接口:Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出 CloneNotSupportedException异常。
浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。 可以看下,原型对象的成员变量是引用类型Car,确实指向同一地址,没有新城新的引用。
package lesson1211; public class Student implements Cloneable {
private int number;
private Car car; public Student(int number, Car car) { this.car = car;
this.number = number;
} //浅复制
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
protected Object clone(){ Student stu = null;
try {
stu = (Student)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stu;
} /**
* @return the number
*/
public int getNumber() {
return number;
} /**
* @param number the number to set
*/
public void setNumber(int number) {
this.number = number;
} /**
* @return the car
*/
public Car getCar() {
return car;
} /**
* @param car the car to set
*/
public void setCar(Car car) {
this.car = car;
}
} package lesson1211; public class Car { private String name; private int speed; public Car(String name, int speed) {
this.name = name;
this.speed = speed;
} /**
* @return the name
*/
public String getName() {
return name;
} /**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
} /**
* @return the speed
*/
public int getSpeed() {
return speed;
} /**
* @param speed the speed to set
*/
public void setSpeed(int speed) {
this.speed = speed;
} }
package lesson1211; public class TestClone { public static void main(String[] args) {
Car car = new Car(null,20000);
Student stu1 = new Student(3,car);
stu1.setNumber(12345);
stu1.getCar().setName("Baoma"); Student stu2 = (Student)stu1.clone();
System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getCar().getName());
System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getCar().getName()); stu2.setNumber(45678);
stu2.getCar().setName("benchi"); System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getCar().getName());
System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getCar().getName());
}
}
运行结果是:
stu1: 12345 Baoma
stu2: 12345 Baoma
stu1: 12345 benchi //修改stu2的car类型,stu1里面也会变,说明stu1和stu2里面的引用car指向同一个地址。这就是浅复制。
stu2: 45678 benchi
深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
同样是上面的例子,我们怎么来通过Object类的clone来实现深克隆,code如下:
package lesson1211; public class Student implements Cloneable {
private int number;
String name;
private Car car; public Student(int number, String name, Car car) { this.car = car;
this.name = name;
this.number = number;
} //浅复制
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
protected Object clone(){ Student stu = null;
try {
stu = (Student)super.clone(); //浅复制
stu.car = (Car)car.clone(); //深度复制 } catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return stu;
} /**
* @return the number
*/
public int getNumber() {
return number;
} /**
* @param number the number to set
*/
public void setNumber(int number) {
this.number = number;
} /**
* @return the car
*/
public Car getCar() {
return car;
} /**
* @param car the car to set
*/
public void setCar(Car car) {
this.car = car;
} /**
* @return the name
*/
public String getName() {
return name;
} /**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
} package lesson1211; public class Car implements Cloneable { private String name; private int speed; public Car(String name, int speed) {
this.name = name;
this.speed = speed;
} /**
* @return the name
*/
public String getName() {
return name;
} /**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
} /**
* @return the speed
*/
public int getSpeed() {
return speed;
} /**
* @param speed the speed to set
*/
public void setSpeed(int speed) {
this.speed = speed;
} /* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
public Object clone() {
Car car = null;
try {
car = (Car)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return car;
}
} package lesson1211; public class TestClone { public static void main(String[] args) {
Car car = new Car(null,20000);
Student stu1 = new Student(3,null, car);
stu1.setNumber(12345);
stu1.setName("zhangsan");
stu1.getCar().setName("Baoma"); Student stu2 = (Student)stu1.clone();
System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName()); stu2.setNumber(45678);
stu2.setName("zhaosi");
stu2.getCar().setName("benchi"); System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName()); }
}
运行结果:
stu1: 12345 zhangsan Baoma
stu2: 12345 zhangsan Baoma
stu1: 12345 zhangsan Baoma
stu2: 45678 zhaosi benchi //达到了我们想要的深度复制结果。
code主要修改的是Car类,它也必须实现Cloneable接口。 Student在实现clone时,单独在clone 它的引用类型变量。 在student里面添加一个String变量,发现是深克隆。
除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。
JDK中StringBuffer类型,关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个 final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是 SringBuffer对象,而且变量名仍是p): o.p = new StringBuffer(p.toString()); //原来的是:stu.car = (Car)car.clone();
通过以上我们可以看出在某些情况下,我们可以利用clone方法来实现对象的深度复制,但对于比较复杂的对象(比如对象中包含其他对象,其他对象又包含别的对象…..)这样我们必须进行层层深度clone,每个对象需要实现cloneable接口,比较麻烦,那就继续学习下一个序列化方法。
利用串行化来做深复制
所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。
也许你会说,只了解一点点,但从来没有接触过,其实未必如此。RMI、Socket、JMS、EJB你总该用过一种吧,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。
第一次使用Java的对象序列化是做某项目,当时要求把几棵非常复杂的树(JTree)及相应的数据保存下来(就是我们常用的保存功能),以便下次运行程序时可以继续上次的操作。
那时XML技术在网上非常的热,而且功能也强大,再加上树的结构本来就和XML存储数据的格式很像。作为一项对新技术比较有兴趣的我当然很想尝试一下。不过经过仔细分析,发现如果采用XML保存数据,后果真是难以想象:哪棵树的哪个节点被展开、展开到第几级、节点当前的属性是什么。真是不知该用A、B、C还是用1、2、3来表示。
还好,发现了Java的对象序列化机制,问题迎刃而解,只需简单的将每棵树的根节点序列化保存到硬盘上,下次再通过反序列化后的根节点就可以轻松的构造出和原来一模一样的树来。
其实保存数据,尤其是复杂数据的保存正是对象序列化的典型应用。最近另一个项目就遇到了需要对非常复杂的数据进行存取,通过使用对象的序列化,问题同样化难为简。
上面这段是摘抄于帖子:https://blog.csdn.net/pony_maggie/article/details/52091588,我自己目前当然没做过。
对象的序列化还有另一个容易被大家忽略的功能就是对象复制(Clone),Java中通过Clone机制可以复制大部分的对象,但是众所周知,Clone有深层Clone和浅层Clone,如果你的对象非常非常复杂,假设有个100层的Collection(夸张了点),如果你想实现深层 Clone,真是不敢想象,如果使用序列化,不会超过10行代码就可以解决。
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,从而将之排除在复制过程之外。 上例代码修改如下:
package lesson1211; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class Student implements Serializable{
private int number;
String name;
private Car car; public Student(int number, String name, Car car) { this.car = car;
this.name = name;
this.number = number;
} public Object deepClone() throws IOException, ClassNotFoundException{
ByteArrayOutputStream 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(); } /**
* @return the number
*/
public int getNumber() {
return number;
} /**
* @param number the number to set
*/
public void setNumber(int number) {
this.number = number;
} /**
* @return the car
*/
public Car getCar() {
return car;
} /**
* @param car the car to set
*/
public void setCar(Car car) {
this.car = car;
} /**
* @return the name
*/
public String getName() {
return name;
} /**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
} package lesson1211; import java.io.Serializable; public class Car implements Serializable { private String name; private int speed; public Car(String name, int speed) {
this.name = name;
this.speed = speed;
} /**
* @return the name
*/
public String getName() {
return name;
} /**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
} /**
* @return the speed
*/
public int getSpeed() {
return speed;
} /**
* @param speed the speed to set
*/
public void setSpeed(int speed) {
this.speed = speed;
} } package lesson1211; import java.io.IOException; public class TestClone { public static void main(String[] args) throws IOException, ClassNotFoundException{
Car car = new Car(null,20000);
Student stu1 = new Student(3,null, car);
stu1.setNumber(12345);
stu1.setName("zhangsan");
stu1.getCar().setName("Baoma"); //Student stu2 = (Student)stu1.clone();
Student stu2 = (Student)stu1.deepClone();
System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName()); stu2.setNumber(45678);
stu2.setName("zhaosi");
stu2.getCar().setName("benchi"); System.out.println("stu1: " + stu1.getNumber() + " " + stu1.getName()+ " " + stu1.getCar().getName());
System.out.println("stu2: " + stu2.getNumber() + " " + stu2.getName()+ " " + stu2.getCar().getName()); } }
运行结果:
stu1: 12345 zhangsan Baoma
stu2: 12345 zhangsan Baoma
stu1: 12345 zhangsan Baoma
stu2: 45678 zhaosi benchi
确实也实现了深度复制。看上去感觉比clone还简单
两个问题:1 引用类型类可不可以不实现Serializable?例如Car类不实现Serializable? 不可以,必须实现,否则会抛异常
2:如果引用类型是transient,就不能进行序列化? 尝试将private Car car;改为private transient Car car; 在上面例子中会报错,因为序列化进去的Car是null, 后面getCar.getName()会报空指针的。对于没有实现Serializable接口的,序列化时要主动将其定义为transient,只要后面不在调用这种类引用就可以。
虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。
你可以通过添加serialVersionUID属性来解决这个问题。如果你的类是个单态(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。
https://blog.csdn.net/pony_maggie/article/details/52091588
https://blog.csdn.net/w410589502/article/details/54985987
https://blog.csdn.net/tounaobun/article/details/8491392
https://www.cnblogs.com/dolphin0520/p/3700693.html
Java 深拷贝,浅拷贝的更多相关文章
- Java深拷贝浅拷贝
首先,Java中常用的拷贝操作有三个,operator = .拷贝构造函数 和 clone()方法.由于Java不支持运算符重载,我们无法在自己的自定义类型中定义operator=.拷贝构造函数大家应 ...
- Java 深拷贝浅拷贝 与 序列化
一.浅拷贝.深拷贝 浅拷贝会对对象中的成员变量进行拷贝:如果是基本类型,拷贝的就是基本类型的值:如果属性是内存地址(引用类型),拷贝的就是内存地址 : 深拷贝,除了基本类型外,引用类型所引用的对象也会 ...
- Java基础 深拷贝浅拷贝
Java基础 深拷贝浅拷贝 非基本数据类型 需要new新空间 class Student implements Cloneable{ private int id; private String na ...
- Java之浅拷贝与深拷贝
----?浅拷贝 --- 概念 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.简单说,浅拷贝就是只复制所考虑的对象,而不复制它所引用的对象 --- 实现方 ...
- Java 深拷贝和浅拷贝 利用序列化实现深拷贝
Java 深拷贝和浅拷贝 转自:http://www.cnblogs.com/mengdd/archive/2013/02/20/2917971.html 深拷贝(deep clone)与浅拷贝(sh ...
- Java的浅拷贝与深拷贝
Java的浅拷贝与深拷贝 Java中,所有的类都继承Object,Object中有clone方法,它被声明为了 protected ,所以我们但是如果要使用该方法就得重写且声明为public,必须在要 ...
- java 深拷贝与浅拷贝机制详解
概要: 在Java中,拷贝分为深拷贝和浅拷贝两种.java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝. (一 ...
- 一种c#深拷贝方式完胜java深拷贝(实现上的对比)
楼主是一名asp.net攻城狮,最近经常跑java组客串帮忙开发,所以最近对java的一些基础知识特别上心.却遇到需要将一个对象深拷贝出来做其他事情,而原对象保持原有状态的情况.(实在是不想自己new ...
- c# 内存的具体表现- 通用类型系统 深拷贝 浅拷贝 函数传参
c# 通用类型系统 及变量在 深拷贝 浅拷贝 函数传参 中的深层次的表现 在编程中遇到了一些想不到的异常,跟踪发现,自己对于c#变量在内存上的表现理解有偏差,系统的学习并通过代码实验梳理了各种情况下, ...
- python集合增删改查,深拷贝浅拷贝
集合 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的.以下是集合最重要的两点: 去重,把一个列表变成集合,就自动去重了. 关系 ...
随机推荐
- 为什么redis使用单线程还能这么快?
通常来讲,单线程处理能力要比多线程差,但是redis为什么就快了,这主要得益于以下几个原因: 1.纯内存访问,redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是redis达到每秒万级 ...
- 让docker容器开机启动
网上有些文章说,要让docker 的容器自动在开机启动,是写脚本,比如在 rc.local 中写.其实完全没必要这么麻烦,docker 有相关指令,docker run 指令中加入 --restart ...
- 概念:dependency injection, IOC, vs callback
callback function as a dependency of the object that it is being passed into. DI is the process of p ...
- UEFI和GPT下硬盘克隆后的BCD引导修复
UEFI和GPT下硬盘克隆后的BCD引导修复-Storm_Center http://www.stormcn.cn/post/1901.html 当硬盘引导换成GPT,系统启动也变成UEFI后,如果直 ...
- centos7-软件安装-jdk1.8
JDK1.8下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 安装目录 ...
- Linux 入侵检测小结
Linux 入侵检测小结 0x00 审计命令 在linux中有5个用于审计的命令: last:这个命令可用于查看我们系统的成功登录.关机.重启等情况:这个命令就是将/var/log/wtmp文件格式 ...
- Django model 字段类型及选项解析
字段类型选择: AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 ...
- java 中文繁简体转换工具 opencc4j
创作缘由 对于中文的繁简体转换是一种很常见的需求. 但是很多工具类都是简单的做个映射.(使用map,集合,properties)等. 存在一个严重的问题:特殊词组 的转换可能存在问题. OpenCC ...
- 三台linux集群hadoop,在此上面运行hive
---恢复内容开始--- 一,准备 先有三台linux,对hadoop集群的搭建. eddy01:开启一个hdfs的老大namenode,yarn的老大ResourceManager其中进程包括(No ...
- python学习笔记_week25
note Day25 - 博客 - KindEditor - beautifulsoup4对标签进行过滤 - 单例模式 - 事务操作 - from django.db import transacti ...