二、对于所有对象都通用的方法
1. 覆盖equals时请遵守通用约定
不需要覆盖equals的情况:
-
类的每个实例本质上都是唯一的。代表的是活动实体而不是值,例如Thread;
-
不关心类是否提供逻辑相等的测试功能。如java.util.Random;
-
父类已经覆盖了equals,从父类继承过来的行为对于子类也是合适的。例如:大部分的Set实现都从AbstractSet继承equals实现。
-
类是私有的或者包级私有的,可以确定它的equals方法永远不会被调用
需要覆盖equals的情况:
对于“值类(value class)”来说,它们需要判断“逻辑相等”,且超类还没有覆盖equals
实现期望的行为,这时我们需要覆盖equals
方法。
值类:例如,Integer
、Date
,仅仅表示一个值,我们在调用equals
时希望知道它们逻辑上是否相等,而不是想了解他们是否指向同一个对象。
覆盖equals应遵守的约定 (来自Object规范【JavaSE6】):
自反性
对于任何非null
的引用值x,x.equals(x)
必须返回true
。
对称性
对于任何非null
的引用值x和y,如果x.equals(y)
返回true
,则y.equals(x)
也必须返回true
。
传递性
对于任何非null
的引用值x、y和z,如果x.equals(y)
返回true
且y.equals(z)
返回true
则x.equals(z)
也必须返回true
。
一致性
对于任何非null
的引用值x和y,只要equals
方法内部比较所用到的字段内容没有被修改,那么多次调用x.equals(y)
就会一致地返回true,或者false。
非空性
所有比较的对象都不为null
,如下代码:
@Override
public boolean equals(Object o){
if(null == o){
return false;
}
......
}
上述代码是不必要的。我们只需要用instanceof
来判断参数类型,如果参数时null
那么instanceof
一定会返回false。同时instanceof
检查是必须要进行的,因为假设参数是错误类型,又不进行instanceof
检查将会抛出ClassCastException异常。因此,正确的非空性判断为:
@Override
public boolean equals(Object o){
if(!(o instanceof MyClass)){
return false;
}
MyType mt = (MyType) o;
......
}
特别注意:不要将equals声明中的Object对象替换为其他的类型。原因是这个方法并没有覆盖Object.equals!参数类型不正确。在原有的equals方法的基础上,重载了一个强类型的equals方法。@Override注解在这里面就起作用了。
2. 覆盖equals时总要覆盖hashCode
否则将会导致该类无法结合所有基于散列的集合一起正常工作(如HashMap、HashSet和HashTable)。
覆盖hashCode应遵守的约定 (来自Object规范【JavaSE6】):
-
在应用程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
-
如果两个对象根据equals(Object)方法比较是相等的,则调用这两个对象中任意一个对象的hashCode方法都必须产生相同的整数结果。
-
如果两个对象根据equals(Object)方法比较是不相等的,hashCode不一定产生不同的整数结果。
特别注意:如果一个类是不可变的,且计算散列码的开销比较大,可以考虑把散列码缓存在对象内部,而不是每次请求都重新计算散列码。
3. 始终要覆盖toString
(1)当对象被传递给println、printf,字符串联操作符+、assert或者被调试器打印出来时,toString会自动调用。
(2)在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息。
(3)无论是否指定格式,都用该在文档中明确表明你的意图;都应为toString返回中包含的所有信息,并提供一种编程式的访问途径。例如PhoneNumber类应该包含针对area code、prefix和line number的访问方法。
4. 谨慎地覆盖clone
Cloneable接口的目的是作为对象的一个mixin接口,表明这个对象允许克隆。
其主要的缺陷在于缺少一个clone方法,Object的clone方法是受保护的,如果不加借助于反射,就不能因为一个对象实现了Cloneable接口,就可以调用clone方法。
这个接口的作用决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportException。
这样的副作用是:无需调用构造器就可以创建对象
关于Clone方法应遵守的约定 (来自Object规范【JavaSE6】):
- 创建和返回该对象的一个拷贝,这个拷贝的精确含义取决于该对象的类。
- 一般来说:对于任何对象x,表达式x.clone() != x 为true 且 x.clone().getClass() == x.getClass() 为true,x.clone().equals(x)为true,但这些都不是绝对的要求。
- 拷贝对象往往会创建它的类的一个实例,但它同时也会要求拷贝内部的数据结构,过程中没有调用构造器。
5. 考虑实现Comparable接口
(1)compareTo方法没有在Object中声明,它是Comparable接口中唯一的方法。compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较,它还是个泛型。
(2)类实现了Comparable接口,就表明它的实例具有内在的排序关系,为实现了Comparable接口的对象数组进行排序可以直接Arrays.sotr(a)
。一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。事实上,Java平台类库中所有的值类都实现了Comparable接口。
(3)什么时候考虑实现Comparable接口:正在编写一个值类,它具有非常明显的内在排序关系,比如按照字母排序、数值排序或者年代排序,那就应该坚决考虑实现这个接口。
(4)compareTo方法的通用约定:将这个对象与指定的对象进行比较。当该对象小于、等于或者大于指定对象的时候,分别返回一个负整数、零或者正整数、如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。
- 实现者必须确保所有的x和y都满足:
sgn(x.compareTo(y)) == -sign(y.compareTo(x))
。也暗示着:当且仅当y.compareTo(x)
抛出异常时,x.compareTo(y)
抛出异常。 - 实现者还必须确保这个比较关系是可传递的:
(x.compareTo(y)>0 && y.compareTo(z)>0)
暗示着x.compareTo(z)>0
。 - 实现者必须确保
x.compareTo(y)==0
暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
。 - 强烈建议
(x.compareTo(y)==0) == (x.equals(y))
。如果违反这个条件请予以说明:“注意:该类具有内在的排序功能,但与equals不一致。”
(5)如果一个类有多个关键域,那么按什么样的顺序来比较这些域是非常关键的,必须从最关键的域开始,逐步进行到所有的重要域。