clone方法使用需知
2020-03-16 本文已影响0人
睦月MTK
statement:本篇内容只是建立在我目前经验的基础之上,必然有不完善甚至是不正确的地方,请谨慎阅读,如果能指出错误与不足之处,更是不甚感激
一、使用条件
- 必须先实现
Cloneable
接口。如果不实现Cloneable接口,直接使用clone方法将会抛出CloneNotSupportedException
异常。 - 如果父类没有公有的clone方法,应该重写clone方法,并至少将其访问控制权限扩大为public
二、什么样的类不需要重新覆盖clone方法
- 不可变类以及事实不可变类
像这样状态不会改变的类的对象有一个就可以了,完全没有需要一个克隆体的必要 - 单例模式的类
- 父类已经有了可靠的公有clone方法,且子类没有添加新的状态,如:
- 子类没有添加任何新的字段
- 子类仅仅添加了基础类型的字段
- 子类新添加的引用类型的字段所指向的对象没有状态被改变的可能
三、如何实现一个可靠的clone方法
实现一个可靠的clone方法,大体需要遵循下列步骤:
- 调用super.clone获取克隆出来的对象
- 检查是否有引用类型的字段
- 如果有,检查该引用类型的字段指向的对象的状态是否是有被改变可能的
- 如果是,检查该字段是否是final修饰的
- 如果是,应当考虑能否去掉final修饰,否则将无法进行深克隆
- 如果不是,应当进行深克隆
- 如果不是,直接返回super.clone克隆出来的对象
- 如果是,检查该字段是否是final修饰的
- 如果没有,直接返回super.clone克隆出来的对象
- 如果有,检查该引用类型的字段指向的对象的状态是否是有被改变可能的
- 判断该类是否是被设计用于继承的
- 如果是,应当保持clone方法的访问权限为protected,返回类型为Object,同时抛出
CloneNotSupportedException
异常,并且不应当实现Cloneable接口,应当将是否具有克隆能力的选择权交由子类来决定 - 如果不是,应当更改clone方法的访问权限为public,返回类型为当前类型,且不建议抛出
CloneNotSupportedException
异常,应当使用try-catch内部消化该异常,并抛出一个非受检异常。
- 如果是,应当保持clone方法的访问权限为protected,返回类型为Object,同时抛出
例子:
@Override
public Student clone(){
try {
return (Student) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
注意:
- clone过程也相当于一个构造对象的过程,虽然没有调用构造器。所以,也要注意不要提早让未完整的克隆对象发布,比如在将克隆对象调整到正确的状态之前,将该对象发布给另一个线程的某种方法,又或者是在clone内部调用了可能会被子类覆盖的新方法。要确保第二点,你应当对这些方法加上private或者final修饰以确保子类不会将其覆盖。
- 其实作为一个克隆对象,并不是非得所有的状态和原对象相同,比如唯一序列号、创建时间等等,这些域应当有自己的值。
四、深克隆
Q--什么是深克隆?为什么要进行深克隆?
A--因为Objec提供的clone方法只能够做到将字段的值复制到新对象中去,这个特点对于引用类型的字段来说就很糟糕了,这样会使得原体与克隆体和同一个对象关联,修改那个被关联对象的状态,原体和克隆体的状态会同时发生变化,这是不可容许的。所以就要有深克隆,深克隆负责在将一个完全独立于原对象,且又和原对象状态相同的对象交付给使用者。
Q--如何进行深克隆?
A--原理其实很简单,就是对引用类型的字段重新进行修改,修改为调用引用指向的对象的clone方法创建出来的对象。
例子:
public class Test implements Cloneable{
private int[] arrInt = {1,2,3};
@Override
public Test clone(){
try {
Test studentClone = (Test) super.clone();
studentClone.arrInt = arrInt.clone();
return studentClone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public static void main(String[] args) {
Test testOrigin = new Test();
Test testClone = testOrigin.clone();
System.out.println("testOrigin.arrInt == testClone.arrInt ====> "+ (testOrigin.arrInt.equals(testClone.arrInt)));
System.out.println("testOrigin.arrInt equal testClone.arrInt ====> "+ (Arrays.equals(testOrigin.arrInt, testClone.arrInt)));
}
}
/* result:
testOrigin.arrInt == testClone.arrInt ====> false
testOrigin.arrInt equal testClone.arrInt ====> true
*/
注意:
- 只要克隆的对象种存在状态可能变化的引用,就需要深克隆,所以深克隆中进行深克隆是很常见的事。
五、总结
一句话,没有绝对的必要不要去启用clone方法。作为替代,你可以尝试下“拷贝构造器”,像是这样
public Test newInstance(Test test) {
Test cloneTest = new Test();
cloneTest.arrInt = Arrays.copyOf(arrInt, arrInt.length);
return cloneTest;
}
当然实际上并不一定要以相同的类型返回,决定权完全在你手里。
参考文档: