×

消息

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View e-Privacy Directive Documents

You have declined cookies. This decision can be reversed.

前一阵子准备雅思考试,没时间继续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;
}

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

提交评论


安全码
刷新