clone方法
方法的声明
protected native Object clone() throws CloneNotSupportedException;
- 这是一个本地native方法。(依赖本地方法实现创建对象,不同于new对象)
- throws CloneNotSupportedException是方法抛出异常的声明,这里我们先不管,后面异常的章节会讲解。
方法的作用
当你在程序中有这种需求,即希望:
- 得到一个和原先对象,完全独立的新对象。
- 成员仍和原先对象一致。
有这种做法时,普通的做法就是再new一个一模一样的,但学习clone方法后,你就可以用该方法来做这个事情了。Object类当中的clone方法的默认实现,就是得到一个独立的,和原先对象成员一致的新对象。
方法使用步骤
直接调用的话,肯定是有些问题的,这里我们就来研究一下clone方法的使用步骤:
- 第一步,修改访问权限
clone()方法的访问权限是protected。让一个类自身克隆自身,一般都没有多大意义,所以建议在子类中重写方法访问权限。所以为了能够在类的外部调用该类的clone方法,就需要在该类中重写clone方法的访问权限。 - 第二步,修改返回值类型,从Object改为自身类型
很显然Object当中的克隆方法的默认实现,只会得到一个一模一样且独立的对象,肯定不可能改变对象的类型。建议在子类方法中重写这个返回值类型。 - 第三步,实现接口 java.lang.Cloneable
一个类想要做克隆操作,必须要先实现一个接口 java.lang.Cloneable,表示该类允许进行克隆。
如果一个类没有实现接口java.lang.Cloneable,又要强行进行克隆操作,就会抛出异常CloneNotSupportedException。
Cloneable接口是一个空接口,里面没有任何内容。
克隆使用中的细节问题
Cloneable接口
Cloneable接口是一个空接口,里面没有任何内容。那么让类去实现一个空接口,有什么意义呢?
实现空接口虽然没有得到任何成员,但这个类的数据类型就发生了一些变化。
让这个类从原先不是这个接口的子类,变成了接口的子类。一旦成为接口的子类,就可以使用instanceof关键字进行类型的判断,判断到底是否该接口。从而就可以根据不同的情况,做出不同的处理。
像Cloneable这种没有声明定义任何成员的,一个空接口,它其实就起到一个标记的作用,称之为"标记接口"。
被Cloneable标记的类是允许做克隆操作的,反之不允许。JDK中的标记接口,以后还会见到。
创建对象的方式
clone方法是一种新的创建对象的方式,和new对象的方式是平行的关系,是独立的关系。
调用clone方法得到对象的过程,是依赖于本地方法实现的,不会去调用构造器。
方法体的重写
在进行克隆操作时,正常情况下,我们使用Object类当中的默实现就足够了,不需要重写实现。但假如你真的有需求,对于某个对象的引用x,JDK文档中也规定了一些重写的原则:
- x.clone() != x 为 true
- x.clone().getClass() == x.getClass() 一般也为true
- x.clone().equals(x) 一般情况下也为true
上述规定告诉我们:
- 克隆必须是一个新的独立的对象
- 克隆最好不要改变数据类型,除非你真的有需要。
- 克隆后的两个对象调用equals方法,应该返回true。前提是,必须按照成员变量的取值重写equals方法。
深度克隆
如果类中有引用数据类型的成员变量,那么clone方法的使用就要格外注意了:
- Java当中,Object类的clone方法的默认实现是完全直接拷贝一份成员变量。
- 对于基本数据类型的成员变量来说,没有任何问题,直接拷贝值。
- 但对于引用数据类型而言,拷贝的是引用。这意味着克隆后的引用和原先的引用指向同一个对象。
- 这样的话,使用任何一个引用去修改对象的状态,都会互相影响,这样的两个对象就不是完全独立的了。
浅克隆:像以上Object类当中的clone方法的实现,直接拷贝一份成员变量,引用数据类型克隆后,两个引用指向同一个对象,这样的克隆我们称之为"浅克隆"。
深克隆:对应的,如果能够让引用数据类型成员变量之间也能相互独立,克隆后获取真正独立的两个对象。我们称之为"深克隆"。