概览
Object是一个具体得类,设计它的目的是为了扩展,它所有的非final方法(equals、hashCode、toString、clone和finalize)都有明确的通用约定,因为他们被设计成要覆盖的,任何一个类,它覆盖这些方法的时候,都有责任遵守这些通用约定,其他依赖于这些约定的类,就无法正常工作。
覆盖Equals时需遵守的约定
==与Equals的区别:
基础变量:boolean、short、int、long、float、double、char、byte
在对比非基础变量类型的时候,比如String,==实际上对比的是两个对象在内存的位置信息,如果是两个不相同的对象则会产生false,即使两个对象的值相等。String类型重写了Object列的Equals方法实现对比值。
覆盖equals方法看起来比较简单,但是覆盖方式会导致错误,并且会造成严重的后果。最容易避免这种问题的方法就是不覆盖equals方法,在这种情况下,类的每个实例都只与他自身相等,如果满足了下面的任意一个条件,就是所期望结果:
- 类的每个实例本质上都是唯一的
- 不关心类是否提供了逻辑相等
- 超类已经覆盖了equals,从超类继承过来的行为对子类也是合适的
- 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用
在这些情况下不应该在尝试去覆盖equals,或者说覆盖equals也没有实际的意义,如满足1,去对比两个Thread类是否相等,因为对象具有唯一性,也没有具体值,equals比多此一举,,Thread表示的是活动,不是值比较没有任何意义,再比如设计了一个不需要考虑值的类,此时在比较也没有任何意义。第三条可以举例如Set和List都从父类AbstractSet中继承了equals的实现,直接拿来吧,你,使用即可,也不需要使用equals。。假如设计了一个类,具有自己特有的逻辑属性,此时我们就应该覆盖equals方法。
比如说设计了一个我最爱玩的英雄联盟类,定义了一个亚索对象,一个维嘉对象,这俩肯定不等,但是如果定义了一个黑夜使者亚索和西部牛仔亚索,他俩套了不同的皮,但是都是亚索,逻辑上都是哈撒鸡,所以equals应该返回True。我们在比较这种英雄类的时候,当队友选了西部牛仔,由于equals返回了true,所以你没法选择亚索这个英雄。,你是想知道是不是亚索,而无关是不是完全一样的对象,这时候要去覆盖equals。也遵循数学上的定义:自反性、对称性、传递性、一致性。
实现高质量Equals的技巧:
- 使用==操作符检查“参数是否是这个对象的引用”,如果是,则百分之百相等。
- 使用instanceof操作符监测“参数是否为正确的类型”。如果不是,则返回false
- 把参数转换为正确的可对比的类型
- 对于自定义类中的每个“关键域”,检查参数中的域是否与该对象中对应的域相匹配。
- 覆盖Equals一定要覆盖hashcode
- equals方法不能太过复杂
- 不要将Equals中的object对象替换为其他的类型。
覆盖equals总要覆盖hashcode
一个很常见的错误根源在于没有覆盖hashcode,在每一个覆盖了equals方法的类中,也必须覆盖hashcode方法,如果不这样做会贝贝Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作。
import java.util.HashMap;
public final class PhoneNumber {
private final short areaCode;
private final short preFix;
private final short lineNumber;
public PhoneNumber(int areaCode,int preFix,int lineNumber){
//范围审核
rangeCheck(areaCode,999,"area code");
rangeCheck(preFix,999,"preFix");
rangeCheck(lineNumber,9999,"lineNumber");
this.areaCode = (short) areaCode;
this.preFix=(short)preFix;
this.lineNumber=(short)lineNumber;
}
private static void rangeCheck(int arg,int max,String name){
if(arg<0 || arg > max){
throw new IllegalArgumentException(name+": "+arg);
}
}
@Override
public boolean equals(Object o){
if(o==this){
return true;
}
if(!(o instanceof PhoneNumber)){
return false;
}
PhoneNumber ph=(PhoneNumber) o;
return ph.lineNumber==lineNumber && ph.preFix==preFix && ph.areaCode == areaCode;
}
public static void main(String[] args) {
HashMap<PhoneNumber,String> hashMap=new HashMap<PhoneNumber, String>();
hashMap.put(new PhoneNumber(707,867,5309),"逢坂大河");
System.out.println(hashMap.get(new PhoneNumber(707,867,5309)));
}
}
测试显示为null,由于PhoneNumber没有覆盖hashCode方法,导致两个相等的实例具有不同的散列码。
@Override
public int hashCode(){
int result=17;
result = 31*result+areaCode;
result = 31*result+preFix;
result = 31*result+lineNumber;
return result;
}