欢迎大家来到IT世界,在知识的湖畔探索吧!
首先我告诉你hashCode equals要覆盖的话一定要同时覆盖,否则会出现很多奇怪的问题,还有hashCode用不好会导致内存泄漏哦,请先记下这些结论,下面我们慢慢分析。
假如你想查找一个集合中是否包含某个对象,那么代码应该怎么写呢?
通常的做法是逐一取出每个元素与要查找的对象一一比较,当发现两者进行equals比较结果相等时,则停止查找并返回true,否则,返回false。但是这个做法的一个缺点是当集合中的元素很多时,譬如有十万个元素,那么逐一的比较效率势必下降很快。
于是有人发明了一种哈希算法来提高从该集合中查找元素的效率,这种方式将集合分成若干个存储区域(可以看成一个个桶),每个对象可以计算出一个哈希码,拥有不同哈希码的对象被放入到不同的区域里面,如下图所示:
假设现在全部的对象都已经根据自己的hashCode值存储在不同的存储区域中了,那么现在查找某个对象,不需要遍历整个集合了,现在只需要计算要查找对象的的hashCode,然后找到该hashCode对应的存储区域,在该存储区域中来查找就可以了,这样效率也就提升了很多(这个地方如果更改了与计算hashcode相关元素的值,会存在内存泄露问题,后边会有专门的例子)。说了这么多相信你对hashCode的作用有了一定的了解,下面就来看看hashCode和equals的区别和联系。
hashCode 和 equals 的区别?
JDK对equals(Object obj)和hashCode()这两个方法的定义和规范:在Java中任何一个对象都具备equals(Object obj)和hashCode()这两个方法,因为他们是在Object类中定义的。
equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。
hashCode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
为什么重写了equals还需要重写hashCode呢?
一般来说涉及到对象之间的比较大小就需要重写equals方法,但是为什么重写了equals还需要重写hashCode呢?
其实如果不这样做代码也可以执行,只不过会隐藏bug。
类的对象如果会存储在HashTable,HashSet, HashMap等散列存储结构中,那么重写equals后一定也要重写hashCode,否则会导致存储数据的不唯一性(会存储两个equals相等的数据)。而如果确定不会存储在这些散列结构中,则可以不重写hashCode。
但是,谁能保证后期不会存储在这些结构中呢,况且重写了hashCode也不会降低性能,因为在线性结构(如ArrayList)中是不会调用hashCode,所以为了保险起见一定要同时重写equals和hashCode。
不同时重写equals和hashCode会有什么问题?
下面我们以HashSet为例为大家讲解, HashTable HashMap等其他散列结构都会存在同样的问题
我先说下将对象放入到HashSet的大体流程:
从上图我们可以清晰的看到,想要判断一个对象是否存在于HashSet需要两个条件
- 对象与HashSet中某一个对象的hashcode相等
- 对象与HashSet中某一个对象equals相等
我们可以看一下具体的代码实现,HashSet 增加元素的add方法最终调用的是HashMap的put方法,所以我们把他们关键代码都贴出来
HashSet
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
欢迎大家来到IT世界,在知识的湖畔探索吧!
HashMap
欢迎大家来到IT世界,在知识的湖畔探索吧! public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
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;
}
下面演示具体的问题:
- 覆盖equals(Object obj)但不覆盖hashCode(),导致数据不唯一性
class Point {
private int x;
private int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
//只是覆盖了equals没有覆盖hashCode
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Point other = (Point) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
@Override
public String toString() {
return "x:" + x + ",y:" + y;
}
}
public class HashCodeTest {
public static void main(String[] args) {
Collection set = new HashSet();
//业务上我们会认为p1 p2是一样的,而且他们的equals也相等
Point p1 = new Point(1, 1);
Point p2 = new Point(1, 1);
System.out.println(p1.equals(p2));
set.add(p1); //(1)
set.add(p2); //(2)
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
System.out.println(object);
}
}
}
结果p1 和 p2 都被放入了 HashSet, 是不是和你以前的理解不一样呢
欢迎大家来到IT世界,在知识的湖畔探索吧!true
x:1,y:1
原因分析:最核心的原因就是两个对象的hashCode不一致
- 当执行set.add(p1)时(1),集合为空,直接存入集合;
- 当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,因为没有覆盖hashCode方法,所以jdk使用默认Object的hashCode方法,返回内存地址转换后的整数,因为不同对象的地址值不同,所以这里不存在与p2相同hashCode值的对象,所以直接存入集合。
- 覆盖hashCode方法,但不覆盖equals方法,仍然会导致数据的不唯一性
class Point {
private int x;
private int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public String toString() {
return "x:" + x + ",y:" + y;
}
}
public class HashCodeTest {
public static void main(String[] args) {
Collection set = new HashSet();
//业务上我们会认位p1 p2是一样的,而且他们的equals也相等
Point p1 = new Point(1, 1);
Point p2 = new Point(1, 1);
System.out.println(p1.equals(p2));
set.add(p1); //(1)
set.add(p2); //(2)
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
System.out.println(object);
}
}
}
结果还是p1 和 p2 都被放入了 HashSet
原因分析:最核心的原因就是两个对象的equals不一致
- 当执行set.add(p1)时(1),集合为空,直接存入集合;
- 当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,这里覆盖了hashCode方法,p1和p2的hashCode相等,所以继续判断equals是否相等,因为这里没有覆盖equals,使用Object 的equals,即比较两个对象的内存地址,所以这里返回false,所以也将p2存入集合。
综合上述两个测试,要想保证元素的唯一性,必须同时覆盖hashCode和equals才行。
更改与计算hashCode有关变量的值会存在内存泄漏问题
class Point {
private int x;
private int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
//hashcode的生成和 x y 都有关系
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Point other = (Point) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
@Override
public String toString() {
return "x:" + x + ",y:" + y;
}
}
public class HashCodeTest {
public static void main(String[] args) {
Collection set = new HashSet();
Point p1 = new Point(1, 1);
Point p2 = new Point(1, 2);
set.add(p1);
set.add(p2);
//更改了与计算hashcode 有关的x y 的值
p2.setX(10);
p2.setY(10);
set.remove(p2);
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
System.out.println(object);
}
}
}
结果是p2并没有被删除,是不是很神奇
x:1,y:1
x:10,y:10
原因分析:hashcode变了,导致查找的区域错误
假设p1的hashCode为1,p2的hashCode为2,在存储时p1被分配在1号桶中,p2被分配在2号桶中。
这时修改了p2中与计算hashCode有关的信息(x和y),当调用remove(Object obj)时,首先会查找该hashCode值的对象是否在集合中。
假设修改后的hashCode值为10, 这时可能会去3号桶中查找,当然查找结果空,所以jdk认为该对象不在集合中,所以不会进行删除操作。
然而用户以为该对象已经被删除,导致该对象长时间不能被释放,造成内存泄露。
解决方法:
尽量不要在执行期间修改与hashCode值有关的对象信息,如果非要修改,则必须先从集合中删除该元素,修改完成后后再加入集合中。
总结
- 在散列结构中判断对象相等的条件是 hashcode相等以及equals 相等
- 覆盖equals时一定要覆盖hashCode
- 尽量不要修改与计算hashcode相关属性的值,非要修改的话先删除该元素,修改完再加入集合
喜欢我的文章给个关注吧,也可关注 WX “Java大讲堂” 获取最新干货, 私信笔者送一套60G Java学习资料
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/35359.html