什么是clone?
GoF设计模式里有一个模式为原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
简单的说就是clone一个对象实例。使得clone出来的copy和原有的对象一模一样。clone方法的通用约定非常弱,一般的含义是,对任何对象x,表达式:
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
但是这些都不是绝对的要求。我们的克隆是:创建它的类的新实例,同时拷贝内部的数据结构。
Object的clone:
堆上的内存存储解释的话(不计内务内存),对一个对象a的clone就是在堆上分配一个和a在堆上所占存储空间一样大的一块地方,然后把a的堆上内存的内容复制到这个新分配的内存空间上。
Object的clone是将对象简单的实行域对域的copy,实例中仅仅拷贝了一个对User的引用,还是指向的同一User对象。即:Shallow clone -默认的浅克隆
//*************************** Shallow clone class User{ String name; } class Book implements Cloneable{ User user; String name; int price; @Override protected Book clone() throws CloneNotSupportedException { //Object.clone返回Object,在覆盖clone方法时转换,客户端就不必再转换了 //通则:不要让客户去做任何类库能够替客户完成的事情 return (Book)super.clone(); } } //*************************** Deep clone class User2 implements Cloneable{ String name; int age; @Override public User2 clone() throws CloneNotSupportedException { return (User2) super.clone(); } } class Book2 implements Cloneable{ User2 user; String name; int price; @Override public Book2 clone() throws CloneNotSupportedException { Book2 book2=null; book2=(Book2)super.clone(); if(user!=null){//可变引用类型深克隆 book2.user=user.clone(); } return book2; } } //*************************** /** *@Function: 克隆 *@Author: April *@Date: 2014-3-25 */ public class CloneTest { public static void main(String[] args) throws CloneNotSupportedException { shallow(); System.out.println(); deep(); } static void shallow() throws CloneNotSupportedException{ User user=new User(); user.name="April_1"; Book book=new Book(); book.user=user; book.price=1111; book.name="Java_1"; //clone Book cloneBook= book.clone(); //可变引用类型,仅仅只copy了一个指向同一个对象的引用 cloneBook.user.name="April_2"; //不可变引用类型,浅克隆不会改变其内部状态 cloneBook.name="Java_2"; //基本类型 cloneBook.price=2222; //output: April_2 , Java_1 ,price=1111 System.out.format("%s , %s ,price=%d",book.user.name,book.name,book.price); } static void deep() throws CloneNotSupportedException{ User2 user2=new User2(); user2.name="April_1"; user2.age=11; Book2 book2=new Book2(); book2.user=user2; book2.price=1111; book2.name="Java_1"; Book2 cloneBook2= book2.clone(); cloneBook2.user.name="April_2"; cloneBook2.user.age=22; cloneBook2.name="Java_2"; cloneBook2.price=2222; //output: April_1 , Java_1 ,price=1111,user.age=11 System.out.format("%s , %s ,price=%d,user.age=%d",book2.user.name,book2.name,book2.price,book2.user.age); } }
为什么需要浅copy?
- 效率和简单性,简单的copy一个对象在堆上的的内存比遍历一个对象网然后内存深copy明显效率高并且简单。
- 不给别的类强加意义。如果A实现了Cloneable,同时有一个引用指向B,如果直接复制内存进行深copy的话,意味着B在意义上也是支持Clone的,但是这个是在使用B的A中做的,B甚至都不知道。破坏了B原有的接口。
- 有可能破坏语义。如果A实现了Cloneable,同时有一个引用指向B,该B实现为单例模式,如果直接复制内存进行深copy的话,破坏了B的单例模式。
- 方便且更灵活,如果A引用一个不可变对象,则内存deep copy是一种浪费。Shadow copy给了程序员更好的灵活性。
怎样去实现clone?
-
声明实现Cloneable接口。 否则会抛出CloneNotSupportedException
-
调用super.clone拿到一个对象,如果父类的clone实现没有问题的话,在该对象的内存存储中,所有父类定义的field都已经clone好了,该类中的primitive和不可变类型引用也克隆好了,可变类型引用都是浅copy。
-
把浅copy的引用指向原型对象新的克隆体。(拷贝任何包含内部"深层结构"的可变对象,并用指向新对象的引用代替原来指向这些对象的引用)
-
注:shallow clone还是deep clone主要取决于对象的域是什么性质的。如果是基本类型或不可变引用类型(如:String),就应该用shallow clone,其他引用类型用deep clone