java的深拷贝和浅拷贝

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关键字修饰,序列化的时候就会跳过这个字段的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值