前一阵子准备雅思考试,没时间继续Effective Java,现在考完了,要捡起来。

实现Comparable接口

Comparable接口只有唯一一个方法:

public interface Comparable<T> {
	int compareTo(T t);
}

compareTo方法的返回值是一个整数。对于a.compareTo(b),如果返回负值,则表明a < b;正值表明a > b;如果返回0,则说明a与b相等。如果a与b类型不同,应该弹出ClassCastException。

所有实现了此接口的类的对象都可以被"自然排序",查找、极值、维序等操作也会变得非常简单。比如排序一个这样的对象构成的数组:

Arrays.sort(a);

实现Comparable接口时,要遵守的合约很类似于Object类的equals方法:

 1. 对称:如果a > b,那么必须有b < a

 2. 传递:如果a > b,而且b > c,那么必须有a > c

 3. 一致:如果a = b,那么对任何c,且a > c,则必然有b > c,反之亦然。

 4. 如果用compareTo方法判定两个对象相等,那么用equals方法判定,最好二者也是相等的。这不是一条强制性和约,但它是被强烈建议遵守的。反之,如果这一条不能被遵守,则应在注释和文档中明确指出:

This class has a natural ordering that is inconsistent with equals.

针对上述第4条,有一个有趣的例子:BigDecimal类,它是不遵守第4条合约的。为了看出这个violation的影响,可以建立两个对象,BigDecimal("1.0")和BigDecimal("1.00"),并试着分别把它们加入HashSet和TreeSet:

BigDecimal firstObj = new BigDecimal("1.0");
BigDecimal secondObj = new BigDecimal("1.00");

HashSet<BigDecimal> hashSet = new HashSet<BigDecimal>();
hashSet.add(firstObj);
hashSet.add(secondObj);
System.out.println("Size of HashSet: " + hashSet.size());

TreeSet<BigDecimal> treeSet = new TreeSet<BigDecimal>();
treeSet.add(firstObj);
treeSet.add(secondObj);
System.out.println("Size of TreeSet: " + treeSet.size());

执行上面的代码,输出是:

Size of HashSet: 2
Size of TreeSet: 1

这是因为,对于普通的Collection类,比如HashSet,元素的相等性是由equals方法判定,而BigDecimal("1.0").equals(BigDecimal("1.00"))是返回false的,所以对于HashSet来说,它们是两个不同的元素。而对于有序的Collection,比如TreeSet,由compareTo方法判定相等性,并且BigDecimal("1.0").compareTo(BigDecimal("1.00"))返回0,所以对于TreeSet来说二者是同一个元素,只有一个被加入Set中。由此例可以看出,第4条合约虽然不是强制合约,但如果被破坏,将严重影响程序的行为。

compareTo方法的编码,是逐个比较两个对象的属性,按重要性由高至低进行比较:

 - 对于整数类型的数据,用>和<运算符

 - 对于浮点类型的数据,用Double.compare或者Float.compare方法

 - 对于对象类型,则递归调用属性的compareTo方法,或者进行业务逻辑比较

 - 对于数组类型,则逐个比较其元素

一旦遇到可以决定大小的属性,则立即返回结果。如果所有属性都相等,则返回0。下面是item 9中电话号码的compareTo方法:

public int compareTo(PhoneNumber pn) {
	// Compare area codes
	if (areaCode < pn.areaCode)
		return -1;
	if (areaCode > pn.areaCode)
		return 1;
	// Area codes are equal, compare prefixes
	if (prefix < pn.prefix)
		return -1;
	if (prefix > pn.prefix)
		return 1;
	// Area codes and prefixes are equal, compare line numbers
	if (lineNumber < pn.lineNumber)
		return -1;
	if (lineNumber > pn.lineNumber)
		return 1;
	return 0; // All fields are equal
}

因为compareTo的返回值中,数值并不重要,我们只需要知道其是正值还是负值或者零,所以上面的代码可以有如下优化:

public int compareTo(PhoneNumber pn) {
	// Compare area codes
	int areaCodeDiff = areaCode - pn.areaCode;
	if (areaCodeDiff != 0)
		return areaCodeDiff;
	// Area codes are equal, compare prefixes
	int prefixDiff = prefix - pn.prefix;
	if (prefixDiff != 0)
		return prefixDiff;
	// Area codes and prefixes are equal, compare line numbers
	return lineNumber - pn.lineNumber;
}

做这样的优化需要倍加小心,要对各属性的数值范围有所了解,以避免相减操作结果的超界。

提交评论


安全码
刷新