天天看点

高效Java之覆盖clone方法与实现Comparable的正确姿势

作者:授道者

原则1:谨慎覆盖clone方法

一个类要是能克隆的就必须实现Cloneable接口,并且正确地覆盖clone方法.如何正确地覆盖clone方法呢?

1.如果父类没有正确地覆盖clone方法,则影响了子类clone方法的正确行为;

2.覆盖clone方法时务必用super.clone()调用父类的clone方法,如果一个类不这么做,而是通过调用构造器返回克隆对象,那么其子类可能得到一个错误的类实例(譬如由于类继承关系导致没有正确的初始化),因为其违背了Object类中clone方法体系的定义.

3.clone方法类似构造器方法中的调用链关系,即子类构造器会(非显式)调用父类构造器;

4.如果成员变量中有可变的引用类型,务必使用深拷贝,而非简单地调用super.clone()来浅拷贝对象;

5.在clone方法中,与构造器方法一样,不能调用可覆盖的方法,因为这样会引起不确定的行为;

6.如果实现的是线程安全的类,那么clone方法也要像其他方法一样注意同步(synchronize);

7.接口或为继承而设计的类最好不要实现Cloneable接口,因为实现了Cloneable接口意味着类中必须实现正确的、良好行为的clone方法,其子类也必须实现clone方法,别无选择。如果没有实现Cloneable接口,而想实现克隆对象,可用复制构造器或复制静态工厂方法替代clone方法,这往往是最佳实践。除了克隆数组对象适用于clone方法外,大神级程序员一般都不会实现Cloneable接口,而是用复制构造器或复制静态工厂方法代替。

// Copy constructor
public Yum(Yum yum) { ... };
                     
// Copy factory
public static Yum newInstance(Yum yum) { ... };
                                        
// Clone method for class with references to mutable state
@Override public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}           

原则2:考虑实现Comparable接口

一个类实现了Comparable接口表明了该类的实例是可排序的。在集合框架中,可排序集合(TreeMap、TreeSet)都依赖于类实现的比较方法,所以如果类是可排序的,特别是值对象的类,要考虑实现Comparable接口或提供比较器。与实现equal方法的契约类似,实现比较方法也要满足:

1.反身性/对称性 -- sgn(x.compareTo(y)) == -sgn(y.compareTo(x));

2.传递性 -- (x.compareTo(y) > 0 && y.compareTo(z) > 0) 蕴含 x.compareTo(z) > 0;

3. x.compareTo(y) == 0,对于所有的z蕴含sgn(x.compareTo(z)) == sgn(y.compareTo(z));

4. 强烈推荐,非必须 -- (x.compareTo(y) == 0) == x.equals(y).如果违反了这条规则,最好说明描述.

基本类型的比较推荐用Double.compare/Long.compare方法.多个字段比较时,可以先比较重要的字段,若结果是0,立即返回,否则继续比较下一个字段,直到比较所有字段.

// Multiple-field Comparable with primitive fields
public int compareTo(PhoneNumber pn) {
int result = Short.compare(areaCode, pn.areaCode);
if (result == 0) {
result = Short.compare(prefix, pn.prefix);
if (result == 0)
result = Short.compare(lineNum, pn.lineNum);
}
return result;
}

//尽管有点性能损耗,java8 的实现方式更加简洁
// Comparable with comparator construction methods
private static final Comparator<PhoneNumber> COMPARATOR =
comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn) {
return COMPARATOR.compare(this, pn);
}           

继续阅读