Java - 提高(8) - equals和hashCode

equals和hashCode



HashCode的作用

Object的源码中,hashCode是这样定义的:

public native int hashCode();
JDK API中对HashCode的描述:
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。

(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)


当我们向一个集合中添加某个元素,集合会首先调用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》

你可能感兴趣的:(Java_提高_源码)