1.什么是hashcode
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值,也就是哈希码,哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。
例如:
String str1 = “aa”,
str1.hashCode= 3104
String str2 = “bb”,
str2.hashCode= 3106
String str3 = “aa”,
str3.hashCode= 3104
根据HashCode由此可得出
str1!=str2,
str1==str3
不同的对象有不同的哈希码算法例如:
1、Object类的hashCode返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
2、String类的hashCode根据String类包含的字符串的内容,根据一种特殊算法返回哈希码。只要字符串所在的堆空间相同,返回的哈希码也相同。
3、Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如:
Integer i1=new Integer(100); i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
由此可以看出Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
2.hashcode在Java中的相关引用
在Java中object类的hashCode方法定义如下:
public native int hashCode();
有此可以看出object类并未对其具体实现,该方法的返回值是int,是本地方法。
对于哈希码本身来说就是一种为了提高查找效率的一种算法,因此hashcode适合用于绝大多数的容器,例如:HashSet、HashMap以及HashTable。
举个例子:
当我们向集合中插入一个对象(不允许重复),如果该对象已经存在的情况下不允许插入,在数据量大的情况下调用equals()方法就造成 了效率缓慢问题。使用hashcode(),会先算出新增对象的哈希值如果没有相等的哈希值在该集合中那么就新增,如果存在相应的哈希值,则调用equals()方法判断是否相等。
下面是hashMap的源码:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode 值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将 新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
3.equals方法
object类中定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
在Object类里面提供的Equals()方法默认的实现是,比较的是地址,比较当前对象的引用和你要比较的那个引用它们指向的是否是同一个对象。例如比较流行的面试问题equals和==的区别,在object中两者是相等的,都是对一个对象的指向进行比较。但是在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;
}
这样在String中就变成了对值的比较,比较两者的value是否相等。,这是比较的内容。
一般我们在设计一个类时,需要重写父类的equals方法,在重写这个方法时,需要按照以下几个规则设计:
1、自反性:对任意引用值X,x.equals(x)的返回值一定为true.
2、对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;
3、传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true
4、一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
5、非空性:任何非空的引用值X,x.equals(null)的返回值一定为false
4.两者相似与比较
1)对于equals()与hashcode(),比较通用的规则:
①两个obj,如果equals()相等,hashCode()一定相等
②两个obj,如果hashCode()相等,equals()不一定相等
2)在设计一个类的时候往往需要重写equals方法,比如String类。在重写equals方法的同时,必须重写hashCode方法。
如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生理解的不一致,如在存储散列集合时(如Set类),将会存储了两个值一样的对象,导致混淆,因此,就也需要重写hashcode()。
通俗一点就是在例如hashMap集合中put一个对象的时候其实就是将一个对象的值(属性、哈希值)放入,如果在根据这个对象get取出来时也会考虑它的哈希值,但是get时如果没有重写hashcode()方法,你的新对象就是一个新的哈希值与原对象不想等,应该get时肯定为空。
下面是hashMap中的get方法:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
可以看出get传入对象时会去掉用getEntry方法,getEntry时先算出hash值,如果在容器中不存在该哈希值值直接返回空。
为什么要重写hashCode方法?
我们应该先了解java判断两个对象是否相等的规则。 在java集合中,判断两个对象是否相等的规则是:
首先,判断两个对象的hashCode是否相等 如果不相等,认为两个对象也不相等 如果相等,则判断两个对象用equals运算是否相等 如果不相等,认为两个对象也不相等 如果相等,认为两个对象相等 。我们在equals方法中需要向下转型,效率很低,所以先判断hashCode方法可以提高效率。
两个对象equals()相等,hashcode()一定相等; equals()不相等,hashcode()不确定。
hashcode()不等,equals()也一定不等;hashcode()相等,equals()不确定。