2019独角兽企业重金招聘Python工程师标准>>>
说在前面
有些专家级程序员干脆从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组。
其他方式
可以提供一个构造函数或者工厂去实现clone功能。
相比于clone,它们有如下优势:
- 不依赖于某一种很有风险的、语言之外的对象创建机制;
- 不要求遵守尚未定制好的文档规范;
- 不会与final域发生冲突;
- 不会抛出不必要的受检异常;
- 不需要进行类型转换;
例如,通用集合的实现都提供了一个拷贝构造函数,它的参数类型为Collection或Map。
假如要把一个HashSet拷贝成一个TreeSet:
HashSet s = ...
new TreeSet(s)
如果一定要覆盖clone方法,那么则需要了解以下它的注意事项了。
Clone规范
x.clone() != x //true
x.clone().getClass() == x.getClass() //true
x.clone.equals(x) // true
行为良好的clone方法可以调用构造器来创建对象,构造之后再复制内部数据。
Clone做法
- 所有实现了Cloneable接口的类都应该用一个公有方法覆盖clone;
- 此公有方法首先要调用super.clone,然后在修正需要修正的域;
// 伪代码
class User implements Cloneable {
@Override
public User clone() {
User user = (User)super.clone(); // 1.先调用super.clone
user.set ... // 2.在修正
}
}
Clone要点
如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象,如果类的所有父类都遵守这条规则,那么调用super.clone最终会调用Object的clone方法,从而创建出正确类的实例。这种机制大体上类似于自动的构造器调用链。
简单Clone
如果类中包含的每个域是一个基本类型的值,或者包含的是一个指向不可变对象的引用,那么调用clone被返回的对象则可能正是所需要的对象,在这种情况下不需要在做进一步的处理。
复杂Clone
如果类中包含的域是指向一个可变对象的引用,那么就要小心的对其进行clone。
例如,若类中存在一个Object[]数组,则可以参考一下做法:
// 伪代码
class Stack {
private Object[] elements;
private int size = 0;
@Override
public Stack clone() {
Stack result = (Stack) super.clone();
result.elements = this.elements.clone();
}
}
还有一种情况,若类中存在一个对象或者集合(自定义对象、List、Map等),那么光调用这些对象的clone还不够,例如编写一个散列表的clone方法,它的内部数据包含一个散列桶数组:
// 伪代码
class HashTable implements Cloneable {
private Entry[] buckets = ...
private static class Entry {
final Object key;
Object value;
Entry next;
Entry(key, value, next) ...
}
}
如果只调用了buckets.clone,其实克隆出来的buckets和被克隆的buckets内的entry是引用着同一对象的。
这种情况下,必须单独拷贝并组成每个桶的链表,例如:
// 伪代码
class HashTable implements Cloneable {
private Entry[] buckets = ...
private static class Entry {
final Object key;
Object value;
Entry next;
Entry(key, value, next) ...
}
// 提供一个深拷贝函数
Entry deepCopy() {
return new Entry(key, value, next == null ? null : next.deepCopy());
}
@Override
public HashTable clone() {
try ...
HashTable result = (HashTable) super.clone();
result.buckets = new Enrty[buckets.length];
for(int i=0;i
提供一个深拷贝方法,遍历源对象的buckets,将它拷贝到新对象中。
这种做法有一个确定,如果散列桶很长,很容易导致栈溢出,因为递归的层级太多!
解决这种问题,可以采用迭代(iteration)来代替递归(recursion),修改一下deepCopy方法:
Entry deepCopy() {
Entry result = new Entry(key, value, next);
for (Entry p = result; p.next != null; p = p.next) {
p.next = new Entry(p.next.key, p.next.value, p.next.next);
}
return result;
}
最好还做到
Object的clone方法被声明为可跑出CloneNotSupportedException异常,但是,覆盖版本的clone方法可能会忽略这个声明。公有的clone方法应该省略这个声明,因为不会跑出受检异常的方法用起来更轻松。
如果专门为了继承而设计的类覆盖类clone方法,覆盖版本的clone方法就应该模拟Object.clone的行为:
- 声明为protected;
- 抛出CloneNotSupportedException;
- 不实现CloneableJiekou ;
总结
以上就是对Effective Java第十一条的摘要。