equals和hashCode
HashCode的作用
Object的源码中,hashCode是这样定义的:
public native int hashCode();
JDK API中对HashCode的描述:当我们向一个集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。
若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则散列到其他位置(可以参考HashMap)
这样处理,当我们存入大量元素时就可以大大减少调用equals()方法的次数,极大地提高了效率。
所以hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。
hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,
根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。
HashCode与equals
两个对象的equals相等,这两个对象的hashCode必定相等!
两个对象的equals不相等,这两个对象的hashCode可能相等,可能不相等!
equlas()
超类Object中equals()方法,该方法主要用于比较两个对象是否相等,该方法的源码如下:
我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时"=="是比较两个对象的内存地址,
所以说使用Object的equals()方法是比较两个对象的内存地址是否相同,即:object1.equals(object2)为true,则表示object1和object2实际上是引用了同一个对象。
实际上JDK中,String,Math等封装类都对equals()方法进行了重写。
String的equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
对于这个代码段:if (v1[i++] != v2[j++]) return false;
可以看出String的eqauls()方法是进行内容比较,而不是引用比较,
至于其他的封装都差不多。
在equals()方法中使用getClass进行类型判断
我们复写equals()方法时,一般都是推荐使用getClass来进行类型判断,不推荐instanceof。
因为instanceof的作用是判断其左边对象是否为其右边对象的实例,返回boolean类型的数据。
可以用来判断继承中子类的实例是否为父类的实现。
注意这句话:"可以用来判 断继承中的子类的实例是否为父类的实现",正是这句话在作怪
我们来看一个栗子:
/**
* 父类
*/
public class Person {
public Person(String name) {
this.name = name;
}
/**
* 重写equals方法
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person = (Person) obj;
if (person.getName() == null || name == null) {
return false;
} else {
return name.equalsIgnoreCase(person.getName());// 将此String与另一个String比较,不考虑大小写。
}
}
return false;
}
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 子类
*/
public class Employee extends Person {
private int id;
public Employee(String name, int id) {
super(name);
this.id = id;
}
/**
* 重写equals方法
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Employee) {
Employee emp = (Employee) obj;
return super.equals(obj) && emp.getId() == id;
}
return false;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class Test {
/**
* 上面父类Person和子类Employee都重写了equals()方法,不过Employee比父类多了一个id属性.
*
* @param args
*/
public static void main(String[] args) {
Employee e1 = new Employee("chenssy", 23);
Employee e2 = new Employee("chenssy", 25);
Person p1 = new Person("chenssy");
System.out.println(p1.equals(e1));
System.out.println(p1.equals(e2));
System.out.println(e1.equals(e2));
}
}
输出:true,true,false
上面定义了两个员工和一个普通人,虽然他们同名,但是他们肯定不是同一人,所以按理说结果应该全部是false,和现在的结果对不上。
对于 "e1 != e2" 很好理解,因为他们不仅需要比较name还需要比较id,只是 p1即等于e1也等于e2,这是非常奇怪的,
因为e1 和e2明明是两个不同的类,为什么会出现这种情况呢?
首先p1.equals(e1),是调用p1的equals方法,该方法使用instanceof关键字来检查e1是否为Person类
然后看instanceof:判断其左边对象是否为其右边类的实例,也可以用来判断继承中子类的实例是否为父类的实现。
他们两者存在继承关系,所以结果为true,接着往下走,而两者name又相同,所以结果肯定是true。
所以出现上面的情况就是使用了关键字instanceof,很容易出现bug,所以在重写equals方法时推荐getClass进行类型判断,而不是instanceof。
参考资料:
http://www.cnblogs.com/chenssy/
《Thinking in Java》