第三章 对于所有对象都通用的方法

第十条:覆盖equals时请遵守通用约定

什么情况下不需要覆盖equals?
类的每个实例本质上是唯一的。
类没有必要提供“逻辑相等”的测试功能。
超类已经覆盖了equals,超类的行为对于这个类也是合适的。
类是私有的,或者是包级私有的,可以确定它的equals方法永远不会被调用。

Equals方法实现了等价关系,其属性如下:
自反性、对称性、传递性、一致性、非空性。

实现高质量equals方法的诀窍:

  1. 使用==操作符检查“参数是否为这个对象的引用”。
  2. 使用instanceof操作符检查“参数是否为正确的类型”。
  3. 把参数转换成正确的类型。
  4. 对于该类中的每个“关键”域,检查参数中的域是否域该对象中对应的域相匹配。

最后的一些告诫:
覆盖equals时总要覆盖hashCode。
不要企图让equals方法过于智能。
不要将equals声明中的Object对象替换为其他的类型。

实例:
PhoneNumber.java

package second;

public final class PhoneNumber {
	
	private final short areaCode, prefix, lineNum;
	
	public PhoneNumber(int areaCode, int prefix, int lineNum) {
		this.areaCode = rangeCheck(areaCode,999,"area code");
		this.prefix = rangeCheck(prefix,999,"prefix");
		this.lineNum = rangeCheck(lineNum,9999,"line num");
	}
	private static short rangeCheck(int val, int max, String arg) {
		if(val < 0 || val > max)
			throw new IllegalArgumentException(arg + ":" + val);
		return (short)val;
	}
	@Override
	public boolean equals(Object o) {
		if(o == this)		//==检查是否为这个对象的引用。
			return true;
		if(!(o instanceof PhoneNumber))		//instanceof检查参数是否为正确类型。
			return false;
		PhoneNumber pn = (PhoneNumber)o;	//转换类型
		return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;		//关键域比较
	}

}

第十一条:覆盖equals时总要覆盖hashCode

关于hashCode的约定内容:
在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode方法都必须始终返回同一个值。在一个应用程序与另一个应用程序的执行过程中,执行hashCode方法返回的值可以不一致。
如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中的hashCode方法都必须产生同样的整数结果。
如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中的hashCode方法,则不一定要求hashCode方法必须产生不同的结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

而当我们没有覆盖hashCode的时候,可能会发生,我们能够向HashMap中存入数据,但是,我们却找不到我们存入的数据了,因为当我们通过对应的对象想要找到对应的key的时候,因为没有覆盖hashCode,这时候两个相等的对象的hashCode可能不同,这就导致了key的不同,因此找不到对应的value。

不要试图从散列码计算中排除掉一个对象的关键域来提高性能。

不要对hashCode方法的返回值做出具体的规定,因此客户端无法理所当然的依赖他,这样可以为修改提供灵活性。

第十二条:始终要覆盖toString

提供好的toString实现可以使类用起来更加舒适,使用了这个类的系统也更易于调试。

在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息。

无论是否决定指定格式,都应该在文档中明确表明你的意图。

无论是否指定格式,都为toString返回值中包含的所有信息提供一种可以通过编程访问之的途径。

第十三条:谨慎的覆盖clone

事实上,实现Cloneable接口的类是为了提供一个功能适当的公有的clone方法。为了达到这个目的,类及其所有超类都必须遵守一个相当复杂的、不可实施的,并且基本上没有文档说明的协议。由此得到一种语言之外的机制:它无需调用构造器就可以创建对象。

不可变的类永远都不应该提供clone方法,因为它只会激发不必要的克隆。

实际上clone方法就是另一个构造器,必须确保它不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件。

Cloneable架构与引用可变对象的final域的正常用法是不相兼容的,除非在原始的对象和克隆对象之间可以安全的共享此可变对象。

深度拷贝:不仅克隆了一些基本类型,引用的对象也一并克隆。

克隆复杂对象的最后一种方法是,先调用super.clone方法,然后把结果对象中的所有域都设置成他们的初始状态,然后调用高层的方法来重新生成对象的状态。

简而言之,所有实现了cloneable接口的类都应该覆盖clone方法,并且是公有的方法,他的返回类型为类本身,该方法应该先调用super.clone方法,然后修正任何需要修正的域。一般情况下,这意味着要拷贝任何包含内部深层结构的可变对象,并用只想新对象的引用来代替原来指向这些对象的引用。虽然,这些内部拷贝操作往往可以通过递归的调用clone来完成,但着通常不是最佳方法。如果该类只包含基本类型的域,或者指向不可变对象的引用,那么多半的情况是没有域需要修正。

对象拷贝的更好的方法是提供一个拷贝构造器或拷贝工厂。
优势:他们不依赖于某一种很有风险的、语言之外的对象创建机制他们不要求遵守尚未制定好文档的规范,他们不会与final域的正常使用发生冲突,他们不会抛出不必要的受检异常,他们不需要进行类型转换。

第十四条:考虑实现Comparable接口

由compareTo方法施加的等同性测试,也必须遵守相同于equals约定所施加的限制条件:自反性、对称性、传递性。
最后一条是一条强烈的建议,他只是说明compareTo方法施加的等同性测试,在通常情况下返回quals方法同样的结果。

在JAVA 7版本中,已经在java的所有装箱基本类型的类中增加了静态的compare方法。在compareTo方法中使用关系操作符“和”是非常繁琐的,并且容易出错,因此不再建议使用。

比较器构造方法:compareInt 和 thenComparingInt。

总而言之,每当实现一个对排序敏感的类时,都应该让这个类实现Comparable接口,以便其实例可以轻松的被分类、搜索,以及用在基于比较的集合中。每当在compareTo方法的实现中比较域值时,都要避免用&&操作符,而应该在装箱基本类型的类中使用静态的compare方法,或者在Comparator接口中使用比较器构造方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值