本想研究一下HashMap的实现原理,但是开篇先讲了散列函数hashCode(),于是呢........内容就转变了。
hashCode是对象的哈希值,主要的作用是用来快速查找对象在哈希表中的位置,而不是内存地址。
hashCode()是Java顶级对象Object中的九大方法之一,也就是说每个java对象都有自己的hashCode。通过源码可以看出hashCode()是native方法,也就是非java语言写的外部方法。
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
JDK文档中hashCode( )的描述如下:
hashCode
public int hashCode()
返回对象的哈希码值。 支持这种方法是为了散列表,如HashMap提供的那样 。
hashCode的总合同是:
只要在执行Java应用程序时多次在同一个对象上调用该方法, hashCode方法必须始终返回相同的整数,前提是修改了对象中equals比较中的信息。 该整数不需要从一个应用程序的执行到相同应用程序的另一个执行保持一致。
如果根据equals(Object)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
不要求如果两个对象根据equals(java.lang.Object)方法不相等,那么在两个对象中的每个对象上调用hashCode方法必须产生不同的整数结果。 但是,程序员应该意识到,为不等对象生成不同的整数结果可能会提高哈希表的性能。
尽可能多的合理实用,由类别Object定义的hashCode方法确实为不同对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但Java的编程语言不需要此实现技术。)
结果
该对象的哈希码值。
文档中有说到
“如果根据equals(Object)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
不要求如果两个对象根据equals(java.lang.Object)方法不相等,那么在两个对象中的每个对象上调用hashCode方法必须产生不同的整数结果。 但是,程序员应该意识到,为不等对象生成不同的整数结果可能会提高哈希表的性能。”
也就是说我们应该保证当equals(Object)方法两个对象相等时,两个对象的hashCode()产生相同的整数结果;当equals(Object)方法两个对象不相等时,尽量保证两个对象的hashCode()产生不相同的整数结果;这也就是为什么网上经常说要同时重写equals()和hashCode()方法。
顺便贴一下JDK文档中equals( )的描述
equals()比较的是对象的内存地址。
equals
public boolean equals(Object obj)
指示一些其他对象是否等于此。
equals方法在非空对象引用上实现等价关系:
自反性 :对于任何非空的参考值x , x.equals(x)应该返回true 。
它是对称的 :对于任何非空引用值x和y , x.equals(y)应该返回true当且仅当y.equals(x)回报true 。
传递性 :对于任何非空引用值x , y和z ,如果x.equals(y)回报true个y.equals(z)回报true ,然后x.equals(z)应该返回true 。
它是一致的 :对于任何非空引用值x和y ,多次调用x.equals(y)始终返回true或始终返回false ,没有设置中使用的信息equals比较上的对象被修改。
对于任何非空的参考值x , x.equals(null)应该返回false 。
该equals类方法Object实现对象上差别可能性最大的相等关系; 也就是说,对于任何非空的参考值x和y ,当且仅当x和y引用相同的对象( x == y具有值true )时,该方法返回true 。
请注意,无论何时覆盖该方法,通常需要覆盖hashCode方法,以便维护hashCode方法的通用合同,该方法规定相等的对象必须具有相等的哈希码。
参数
obj - 与之比较的参考对象。
结果
true如果此对象与obj参数相同; false否则。
大家都应该知道String重写Object的hashCode()和equals()方法。
equals源码如下:
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
源码分析:首先比较了内存地址是否相同,若相同,直接返回true;若不相同,每个字符按顺序依次比较,若完全相同,返回true,否则返回false。
hashCode源码如下:
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
源码分析:将全局变量hash赋值给h,如果h==0或对象的value长度大于0(也就意味着对象已经有值,但是还没有生成hashCode),则将对象的value转换为字符数组,然后遍历字符数组,通过字符对应的ASCII码计算得出hashCode。
例如:String test = "Aa";那么test的hashCode=(31 * 0 + A的ASCII码) + 31 * (31 * 0 + A的ASCII码) + a的ASCII码。
我们知道 A的ASCII码是65,a的ASCII码是97。所以计算出来的test的hashCode是2112。
但其实String没有做到:保证当equals(Object)方法两个对象相等时,两个对象的hashCode()产生相同的整数结果;当equals(Object)方法两个对象不相等时,尽量保证两个对象的hashCode()产生不相同的整数结果
因为对于String来说Aa的hashCode与BB的是相等的。
public static void main(String[] args) {
String a = "Aa";
System.out.println("Aa的hashCode:"+a.hashCode());
String b = "BB";
System.out.println("BB的hashCode:"+b.hashCode());
System.out.println("equals:"+a.equals(b));
System.out.println("hashCode:"+(a.hashCode() == b.hashCode()));
}
输出结果
为什么需要同时重写equals()和hashCode() ???
因为我们要保证当equals(Object)方法两个对象相等时,两个对象的hashCode()产生相同的整数结果。
举个例子:我有一个User类,重写了equals方法,当id相同是就视为两个对象相同。未重写hashCode方法。
/**
* @Date: 2019/6/12 16:47
*/
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
}
测试一下
public static void main(String[] args) {
User user1 = new User();
user1.setId(100);
User user2 = new User();
user2.setId(100);
System.out.println("equals:"+user1.equals(user2));
System.out.println("hashCode:"+(user1.hashCode() ==user2.hashCode()));
}
从输出结果来看,重写了equals但没重写hashCode已经违背了当equals(Object)方法两个对象相等时,两个对象的hashCode()产生相同的整数结果的规则。正常程序运行时如果出现这样的情况会造成一些不必要的麻烦。
比如说,在这个基础上,我们把user1和user2同加入Set集合中,我们知道Set中只存放不重复的数据,根据我们重写的equals来看,只要id相同我们就视为两个对象相同,所以放入集合后,集合的size应该是1,因为user1和user2属于同一对象。但是实际运行结果并未我们所想。
public static void main(String[] args) {
User user1 = new User();
user1.setId(100);
User user2 = new User();
user2.setId(100);
Set<User> set = new HashSet<>();
set.add(user1);
set.add(user2);
System.out.println("set集合大小为"+set.size());
}
结果显示集合大小未2,可见Set认为user1和user2不相等,因为他俩的hashCode不相等,而恰恰HashSet在add的时候是通过判断对象的hashCode是否相等来判断重复(详解博客),那么这个时候就需要重写一下hashCode方法,来实现我们想要的效果。
User中重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(id);
}
我们再运行一下程序,运行结果如下:
经过上面的实践,我们一定要牢牢记住同时重写equals和hashCode方法哦~