深入理解java拷贝机制(clone)

其实clone的作用很简单 直接看例子吧

Book book = new Book();
try {
    Book b = book.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
你看 Book b = book.clone() 你把上面new出来的那个book对象直接赋值给b。然后输出一下对象和对象里的数你会发现是这样的

12-23 02:38:38.975 24434-24434/com.example.mvp I/xbh: com.example.mvp.MainActivity$Book@3db47cf
12-23 02:38:38.975 24434-24434/com.example.mvp I/xbh: com.example.mvp.MainActivity$Book@d58d55c
12-23 02:38:38.975 24434-24434/com.example.mvp I/xbh: 1
12-23 02:38:38.975 24434-24434/com.example.mvp I/xbh: 1

可以看到,内存地址已经不一样了,但是里面的值还是一样的。哇,那岂不是直接相当于给你拷贝了一份!换句话说,也就是替你new了一个对象,但是那个对象和你原来的对象里的属性是一模一样的。


当然了,clone不是拿起来就可以用的,你还需要对这个book类进行这样的修改

class Book implements Cloneable{
    public int s = 1;

    @Override
    protected Book clone() throws CloneNotSupportedException {
        return (Book) super.clone();
    }
}
继承一下Cloneable的接口,并且重写clone方法,即可。


看看clone的源码

protected Object clone() throws CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException("Class " + getClass().getName() +
                                             " doesn't implement Cloneable");
    }

    return internalClone();
}
这里可以得知,一个类只有实现了Cloneable接口,才可以使用克隆方法。


浅拷贝

上面虽然克隆了,但是是真的克隆吗?其实他仅仅是克隆了Book对象,但是里面的其他东西,依然是共享的!看例子

class Book implements Cloneable{
    public Page page = new Page();

    @Override
    protected Book clone() throws CloneNotSupportedException {
        return (Book) super.clone();
    }
}
现在我们book里不是int了,而是一个对象。

class Page {
    public int a = 1;
}
现在我们还是一样,先new一个book,再克隆给b,先输出b的page对象的值,肯定是1。你修改b的page对象的值为2,你会惊讶的发现book的page对象的值也变了

Book book = new Book();
try {
    Book b = book.clone();

    System.out.println(b.page.a + "");
    b.page.a = 2;
    System.out.println(book.page.a + "");

} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
输出

12-23 03:08:58.878 16069-16069/? I/System.out: 1
12-23 03:08:58.878 16069-16069/? I/System.out: 2

所以这完全是因为这是浅拷贝,只是把外部的对象拷贝了,里面还是共享的啊。(当然基本类型不会出现这种共享的情况,虚拟机对基本数据类型是很宽容的,会自动为他们 创建副本)。记住,如上面super这样的克隆,都是浅拷贝。


所以我们就必须要谈谈深拷贝了,显然他就是不仅克隆了一个壳,还克隆了里面所有的东西。怎么实现呢?笨办法,把你里面的page对象,也clone一下。


但是你一个一个去搞肯定不现实,不优雅,所以有必要提一提串行化深拷贝。

(下面借鉴自http://www.jb51.net/article/105651.htm)

在框架中,有的时候我们发现其中并没有重写clone方法,那么我们在需要拷贝一个对象的时候是如何去操作的呢?答案是我们经常会使用串行化方法,实现Serializable接口。
去寻找其他的方法来替代深拷贝也是无可奈何的事情,如果采用传统的深拷贝,难道你拷贝一个对象的时候向其中追无数层来拷贝完所有的对象变量么?先不谈这么做的时间消耗,仅仅是写这样的代码都会让人望而生畏。串行化深拷贝就是这样一个相对简单的方法。
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
上面是网上的专业解释,我也不在这里班门弄斧了。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。

1
2
3
4
5
6
7
8
9
10
11
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());
}

虽然这种学院派的代码看起来很复杂,其实只是把对象放到流里,再拿出来。相比较分析判断无数的clone,这样简直是再简单不过了。这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient。

transient:一个对象只要实现了Serilizable接口,这个对象就可以被序列化(序列化是指将java代码以字节序列的形式写出,即我们上面代码前三行写入对象),Java的这种序列化模式为开发者提供了很多便利,可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的所有属性和方法都会自动序列化。但是有种情况是有些属性是不需要序列号的,所以就用到这个关键字。只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。


我个人再提供一种方法,转xml使之dom化或gson解析,主要谈一下gson解析,就是把对象转化成json数据,再解析出来。


总的来说,clone是一个失败的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值