java的Object类有一个clone()方法,这个方法可以实现对象的拷贝。但是这个拷贝是浅拷贝。
浅拷贝和深拷贝有什么区别?
从字面来看,浅拷贝就是只拷贝表面,比较浅;而深拷贝可以拷贝的更彻底。
clone()为什么是浅拷贝?
clone()对当前对象创建了一个新对象,但是新对象中的成员变量都是从当前对象中做的副本。如果当前对象中的成员变量存在非基本类型变量,比如你自己创建的Person类的对象p,那么新对象中的p是直接复制了原来p的引用,也就是指向了相同的Person对象。
而基本类型是没有引用的,没办法就只能复制数据了,反到是真正的拷贝了。
浅拷贝有什么危害呢?
危害显而易现。当你以为你拷贝了一个对象出去,放心的对当前对象进行各种骚操作的时候,殊不知有可能你拷贝出去的对象也已经被修改了。
那么怎么做深拷贝呢?
有两种方法:
(1)重写Object的clone()方法。例子如下:
class A implements Cloneable {
B b;
@Override
protected Object clone() throws CloneNotSupportedException {
A a = (A) super.clone();
a.b = (B) b.clone();
return a;
}
}
class B implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
A里边有B类型的成员变量,重写了clone()方法,主动对b进行拷贝,这样clone新生成的a对象里的b对象也是不一样的了。
但是这样真的是很麻烦,原因有三:1、每个类都要实现Cloneable;2、都要重写clone方法;3、如果有多层嵌套,每一层都要如此操作。
(2)序列化实现深拷贝
class Utils {
public static <T extends Serializable> T DeepClone (Object obj){
T cloneObj = null;
try{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
ByteInputStream byteInputStream = new ByteInputStream(byteArrayOutputStream.toByteArray(),byteArrayOutputStream.size());
ObjectInputStream objectInputStream = new ObjectInputStream(byteInputStream);
cloneObj = (T) objectInputStream.readObject();
objectInputStream.close();
}catch (Exception e){
e.printStackTrace();
}
return cloneObj;
}
}
将对象序列化到IO流,再从IO流中读入对象,即可实现深拷贝。
测试如下:
class Test {
static class Head implements Serializable{
Face face;
int other;
@Override
public String toString() {
return "Head{" + "face=" + face + ", other=" + other + '}';
}
}
static class Face implements Serializable{
Shape shape;
int length;
@Override
public String toString() {
return "Face{" + "shape=" + shape.name() + ", length=" + length + '}';
}
}
enum Shape {
circle, square;
}
public static void main(String[] args) {
Head head1 = new Head();
head1.other = 1;
Face face1 = new Face();
face1.length = 1;
face1.shape = Shape.circle;
head1.face = face1;
Head head2 = Utils.DeepClone(head1);
System.out.println(head1);
head1.other = 2;
Face face2 = new Face();
face2.shape = Shape.square;
face2.length = 2;
head1.face = face2;
System.out.println(head1);
System.out.println(head2);
}
}
结果如下:
Head{face=Face{shape=circle, length=1}, other=1}
Head{face=Face{shape=square, length=2}, other=2}
Head{face=Face{shape=circle, length=1}, other=1}
发现head1的修改对head2没有任何影响。
为什么序列化可以做到深拷贝呢?
纯属猜测:如果要对一个对象进行序列化,那么就需要对其成员变量进行拷贝。如果有的成员变量是其他对象的引用,那么会怎么序列化呢?
很明显这个引用是不会拷贝的,因为引用本身是内存地址啊,序列化之后就要写出去了,等再次读入的时候这个内存地址就变了,留它有啥用,如果是我实现,肯定是不会序列化引用的,而是会序列化这个引用所指向的对象。
而且每个对象被序列化,然后读入的时候,就会重新构造一个对象放在内存里,当然就和以前的对象不一样了,这就实现了真正的深拷贝。
如何避免序列化进行深拷贝?
真的是什么样的需求都会出现,序列化提供了深拷贝,但是又有人觉得强行全部深拷贝太浪费时间和空间。
但是这样的要求还真的不过分。我们知道序列化的常用场景是网络传输,网络速度慢如蜗牛(和cpu计算速度比),而且网络带宽很宝贵,当然是能少传输一点是一点,好钢用在刀刃上。
好了,废话少说,到底该怎么做呢?很简单,不想进行序列化的字段直接用transient关键字修饰,序列化的时候就会跳过这个字段的。