Java的clone()方法详解
- 本文依据“是什么”“做什么”“怎么做”的思路对clone()进行详细讲解
- 本文目录内容
- clone定义与特点
- clone()的简单代码实现
- 例子讲解(引出clone中的“注意点”)
- “浅拷贝”“深拷贝”区别
1、Clone定义特点
- JDK中的解释为:创建并返回该对象的副本
Creates and returns a copy of this object. The precise meaning
of "copy" may depend on the class of the object.
- 下面,总结引申几个特点
- 会返回一个对象,副本的类型和原生对象类型相同
- 复制,clone()被对象调用,所以会复制对象
其实概念性的东西,大家大可不必深究,对其有了解即可,我们只要懂得应用,而且注意其中的问题就好了
2、简单实现
2.1、实现代码(浅拷贝)
- 对于clone的实现其实很简单,默认,某些类是不具备克隆能力,所以需要我们进行改造
- 如何改造呢?换句话说,如何实现clone呢,让类具备克隆能力?
- 实现Cloneable接口
- 重写clone方法
/**
* 简单实现拷贝
*/
public class Book implements Cloneable {
/**
* 1.实现Cloneable接口
* 2.重写clone方法
*/
//重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();//调用父类的clone
}
}
- 就是这么简单
- 就是这么简单
- 就是这么简单
2.2、简单例子
/**
* 简单实现clone的例子
*/
public class CloneOriginal implements Cloneable {
private int i;
//带参构造方法
public CloneOriginal(int i) {
this.i=i;
}
//重写clone方法
@Override
protected Object clone() {
Object object = null;
try {
object = super.clone();//调用父类的clone
} catch (CloneNotSupportedException e) {//异常捕获
e.printStackTrace();
}
return object;
}
//测试例子
public static void main(String[] args) {
CloneOriginal original=new CloneOriginal(10);
System.out.println("原生的i:"+original.i);
//由于CloneOriginal实现了Cloneable接口并重写了clone方法,所以当调用CloneOriginal对象的clone方法时,
//就会克隆出一份CloneOriginal对象的副本,在克隆时,属性和方法均直接copy,
//由于copy属性时,其属性的类型为int,所以是值传递,copy之后的副本和之前的副本没有任何联系
//所以在对副本属性i进行++时,自然不会影响到原对象的属性i的值
CloneOriginal originalCloned=(CloneOriginal)original.clone();
originalCloned.i++;
System.out.println("副本的i:"+originalCloned.i);
System.out.println("原生的i:"+original.i);
}
}
- 可以发现,对副本属性“i”++后,并不影响原生属性“i”的值,原生属性“i”的值一直为“10”
- 这说明对副本对象的引用并不影响原生对象,那么,是不是一直都是这样呢?
- 不是,不是,不是,为什么?
- 若原生对象的属性中含有引用类型,这样会导致原对象属性的引用和副本对象属性的引用都指向同一个对象,这样当副本对象修改引用类型属的某个属性值时,就间接会影响原对象的引用类型属性的属性值
那么如何才能真正达到两者完全分离呢?这就需要用到:深拷贝和浅拷贝。
3、深拷贝和浅拷贝
3.1、new与clone区别
首先我们先对“new”和“clone”进行概念性的了解
Java创建对象方式有两种
- new
- clone()
那么这两者的不同点在于
new:创建对象的时候,首先会校验new的类型,要根据类型来分配内存空间,分配好内存空间后,再调用构造函数,填充对象的各个属性(域),这一步就叫做对象的初始化,执行完构造函数后,一个对象构建完毕。对象构建完毕后,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操作这个对象。
clone:创建对象的时候,也是先分配内存空间,调用clone()的时候,分配的内存和原生对象(即调用clone方法的对象)相同,然后再使用原生对象中对应的各个属性(域)来填充新对象的域,填充完成后,clone()返回,一个新的对象被创建,同样可以把这个新的对象的引用发布到外部。
3.2、复制引用
这一节就是解释为什么“若原生对象的属性中含有引用类型,则其拷贝的就是句柄,这样会导致原对象属性的引用和副本对象属性的引用都指向同一个对象”?
- 下面,我们看看如下一段代码的执行情况
public class Book implements Cloneable {
private int bookId;
private String bookName;
/**
* Book 构造函数
*
* @param bookId
* @param bookName
*/
public Book(int bookId, String bookName) {
super();
this.bookId = bookId;
this.bookName = bookName;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
public class TestDemo {
public static void main(String[] args) {
Book bookOne=new Book(1, "《Java与模式》");
Book bookTwo=bookOne;
System.out.println(bookOne);
System.out.println(bookTwo);
}
}
- 可以发现bookOne和bookTwo的内存空间地址是一样的
- 那么,这说明他们是同一个对象,bookOne和bookTwo只是引用而已,他们都指向了统一个Book对象,new Book(1,”《Java与模式》”)
- 这种情况就叫做“复制引用”
3.3、复制对象(克隆对象)
- 下面看看克隆一个对象是怎么样的
public class TestDemo {
public static void main(String[] args) {
Book bookOne=new Book(1, "《Java与模式》");
Book bookTwo;
try {
bookTwo = (Book)bookOne.clone();
System.out.println(bookOne);
System.out.println(bookTwo);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
- 不难发现,这两个对象的地址完全不同了,也就是说,这是创建了新的对象
- 与“复制引用”不同的就是:不再是把原生对像的地址赋给了一个新的引用变量
3.4、浅拷贝
- 我们再看看Book类里面的代码
- 其实我们上面3.3“复制对象”中实现的就是“浅拷贝”
- 我们仔细看看“浅拷贝”和“深拷贝”有什么区别?
public class Book implements Cloneable {
private int bookId;
private String bookName;
/**
* Book 构造函数
*
* @param bookId
* @param bookName
*/
public Book(int bookId, String bookName) {
super();
this.bookId = bookId;
this.bookName = bookName;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
- 从Book类中代码中,我们可以看到,Book有两个成员变量,一个是基本类型int的bookId,一个是引用类型String的bookName;
还记的2.2中提到的:若原生对象的属性中含有引用类型,这样会导致原对象属性的引用和副本对象属性的引用都指向同一个对象,这样当副本对象修改引用类型属的某个属性值时,就间接会影响原对象的引用类型属性的属性值,
- 下面我们通过图来理解一下
- 首先bookId是int类型,对它的拷贝,明显这是直接将整数值拷贝,就是拷贝了一个新的,这个是毋庸置疑的,其次,再看看bookName是String类型,它只是一个引用,指向一个真正的String对象,这个我们在“复制引用”和“复制对象”中也有理解,
- 对于String类型的拷贝有两种方式,就是浅拷贝/深拷贝
- 直接将原生对象中bookName的引用值拷贝给新对象的bookName值,
- 根据Book对象中bookName指向的字符串对象创建一个新的相同字符串对象,将这个新字符串对象的引用赋给新拷贝的Book对象的bookName成员变量,
public class TestDemo {
public static void main(String[] args) {
try {
Book bookOne = new Book(1, "《Java与模式》");
Book bookTwo = (Book) bookOne.clone();
System.out.println(bookOne);
System.out.println(bookTwo);
System.out.println(bookOne.getBookName() == bookTwo.getBookName() ? "浅拷贝" : "深拷贝");
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
如果两个Book对象中的bookName的地址相同,说明是指向同一个String对象,也就是浅拷贝
下面我们再看一个“浅拷贝”的例子,加深理解
public class Picture {
}
public class Artcile {
public Picture picture;
public Artcile(){
}
public Artcile(Picture picture){
this.picture=picture;
}
}
public class Book implements Cloneable {
//引用对象
public Artcile artcile;
public Book(){
}
public Book(Artcile artcile){
this.artcile=artcile;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) {
try {
Artcile artcile = new Artcile();
Book bookOne = new Book(artcile);
Book bookTwo = (Book) bookOne.clone();
System.out.println(bookOne);
System.out.println(bookTwo);
System.out.println(bookOne.artcile == bookTwo.artcile ? "浅拷贝" : "深拷贝");
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
3.5、深拷贝
- 实现深拷贝的方法
- 对象实现Cloneable接口
- 重写clone方法
- 并且在clone方法内部,把 该对象 引用的对象 也要clone一份
3.5.1、 不彻底深拷贝
public class Picture {
}
public class Artcile implements Cloneable{
//引用对象
public Picture picture;
public Artcile(){
}
public Artcile(Picture picture){
this.picture=picture;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Book implements Cloneable {
//引用对象
public Artcile artcile;
public Book() {
}
public Book(Artcile artcile) {
this.artcile = artcile;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Book book=(Book)super.clone();
book.artcile=(Artcile)artcile.clone();//引用对象拷贝
return book;
}
}
public static void main(String[] args) {
try {
Artcile artcile = new Artcile();
Book bookOne = new Book(artcile);
Book bookTwo = (Book) bookOne.clone();
System.out.println("bookOne与bookTwo是否相同:" + (bookOne == bookTwo));
System.out.println("article是否相同:" + (bookOne.artcile == bookTwo.artcile));
System.out.println("picture是否相同:" + (bookOne.artcile.picture == bookTwo.artcile.picture));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
- Book中引用了Artice类
- Artice类中引用了Picture类
- 在Book类中,拷贝了Artice类
- 在Artice类中,执行的默认clone(),即是并有没有拷贝Picture类,所以这里默认“浅拷贝”
- 对于Book而言,拷贝了Artice对象,所以这是“深拷贝”
- 对于Artice而言,并没有拷贝Picture,所以这是“浅拷贝”
- 最后导致了“不彻底深拷贝”
3.5.2、 彻底深拷贝
public class Picture implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Artcile implements Cloneable{
public Picture picture;
public Artcile(){
}
public Artcile(Picture picture){
this.picture=picture;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
Artcile artcile=(Artcile)super.clone();
artcile.picture=(Picture)this.picture.clone();
return artcile;
}
}
public class Book implements Cloneable {
// "引用类型"对象
public Artcile artcile;
public Book() {
}
public Book(Artcile artcile) {
this.artcile = artcile;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Book book = (Book) super.clone();
book.artcile = (Artcile) artcile.clone();
return book;
}
}
public static void main(String[] args) {
try {
Picture picture=new Picture();
Artcile artcile = new Artcile(picture);
Book bookOne = new Book(artcile);
Book bookTwo = (Book) bookOne.clone();
System.out.println("bookOne与bookTwo是否相同:" + (bookOne == bookTwo));
System.out.println("article是否相同:" + (bookOne.artcile == bookTwo.artcile));
System.out.println("picture是否相同:" + (bookOne.artcile.picture == bookTwo.artcile.picture));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}