hashCode和equals属于Object的方法。
Object中的equals方法的实现:
public boolean equals(Object obj) {
return (this == obj);
}
Object中的hashCode方法是native的,实现和本地机器相关,当然你也可以重写它:
public native int hashCode();
下面我们来分几个点介绍下hashCode和equals这对姐妹花。
1. equals是谁?
Object类中的equals方法和==作用是一样的,即比较的是俩个对象在栈内存中存储的内存地址。而String,Integer等一些类是重写了equals方法,才使得equals和==不同,它们比较的是值相不相等。
所以当自己创建类时,自动继承了Object的equals方法,要想实现不同的等于比较,必须重写equals方法。
来看一个简单的例子:
private static class Coder {
final String name;
final String lang;
final int age;
Coder(String name, String lang, int age) {
this.name = name;
this.lang = lang;
this.age = age;
}
}
public static void main(String[] args) {
Coder coder1 = new Coder("kuang", "java", 27);
Coder coder2 = new Coder("kuang", "java", 27);
System.out.println("coder1 == coder2: " + (coder1 == coder2)
+ " , coder1.equals(coder2): " + coder1.equals(coder2));
}
执行输出:coder1 == coder2: false , coder1.equals(coder2): false
此时equals和==都是比较coder1和coder2在栈内存中存储的内存地址。
如果我们重写下equals:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Coder coder = (Coder) o;
return age == coder.age &&
Objects.equals(name, coder.name) &&
Objects.equals(lang, coder.lang);
}
执行输出:coder1 == coder2: false , coder1.equals(coder2): true
一般可以用IDE直接重写equals和hashCode方法,比如IntelliJ IDEA:
2. hashCode是谁?
hashCode的作用是获取哈希码,也称为散列码。它实际上是返回一个int整数,这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode定义Object中,Java中的任何类都包含有hashCode方法。
虽然,每个Java类都包含hashCode方法。但是,仅仅当创建某个类的散列表时,该类的hashCode才有用,其它情况下如创建类的单个对象,或者创建类的对象数组等hashCode是没有作用的。
上面的散列表指的是:Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet。
也就是说:hashCode在散列表中才有用,在其它情况下没用。在散列表中hashCode的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
还是上面那个例子:
private static class Coder {
final String name;
final String lang;
final int age;
Coder(String name, String lang, int age) {
this.name = name;
this.lang = lang;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Coder coder = (Coder) o;
return age == coder.age &&
Objects.equals(name, coder.name) &&
Objects.equals(lang, coder.lang);
}
}
public static void main(String[] args) {
Coder coder1 = new Coder("kuang", "java", 27);
Coder coder2 = new Coder("kuang", "java", 27);
System.out.println("code1 hashCode = " + coder1.hashCode());
System.out.println("code2 hashCode = " + coder2.hashCode());
System.out.println("coder1 == coder2: " + (coder1 == coder2)
+ " , coder1.equals(coder2): " + coder1.equals(coder2));
}
执行输出:
code1 hashCode = 1625635731
code2 hashCode = 1580066828
coder1 == coder2: false , coder1.equals(coder2): true
可以看到coder1.equals(coder2)时,它们的散列码并不相等。
3. equals和hashCode的关系
(1) 我们不会在HashSet,Hashtable,HashMap等这些本质是散列表的数据结构中用到该类。例如,不会创建该类的HashSet集合,这时equals和hashCode是没有关系的。
(2) 我们会在HashSet,Hashtable,HashMap等这些本质是散列表的数据结构中用到该类。例如,会创建该类的HashSet集合,在这种情况下,该类的equals和hashCode是有关系的:
如果两个对象相等,那么它们的hashCode值一定相同。这里的相等是指,通过equals比较两个对象时返回true。
如果两个对象hashCode相等,它们并不一定相等。因为在散列表中hashCode相等,即两个键值对的哈希值相等,并不一定能得出键值对相等。"两个不同的键值对,哈希值相等",这就是哈希冲突。在这种情况下,若要判断两个对象是否相等,除了要覆盖equals之外,也要覆盖hashCode方法,否则equals无效。
还是上面那个例子,只重写了Coder类的equals方法:
public static void main(String[] args) {
HashSet<Coder> coders = new HashSet<>();
coders.add(new Coder("kuang", "java", 27));
coders.add(new Coder("kuang", "java", 27));
System.out.println(coders.size());
}
打印输出coders的大小是2。
我们再重写下Coder类的hashCode方法:
@Override
public int hashCode() {
return Objects.hash(name, lang, age);
}
打印输出coders的大小是1。
4. 散列表中是如何判断两个对象是否相等的
以上面的HashSet为例,看看它的put方法实现:
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
先判断hash值是否相等,然后再判断equals是否相等,这样也提高了效率,比如HashSet中已有100个元素,那么第101个元素加入HashSet时,它就要调用100次equals方法,这显然会大大降低效率。
在散列表中,两个对象相等(用equals比较相等),hashCode一定相等,而hashCode相等,两个对象不一定相等(哈希冲突)。
至于散列表如何解决哈希冲突,我在《Java篇 - 并发容器之Hashtable源码分析》文章中有详细讲过。
https://blog.csdn.net/u014294681/article/details/85298342
而对哈希算法感兴趣的同学,可以看看这篇文章《深入理解hashcode和hash算法》
https://blog.csdn.net/qq_38182963/article/details/78940047
5. hashCode引起的内存泄漏
所谓内存泄露就是一个对象占用的一块内存,当这个对象不在被使用时,该内存还没有被收回。本例用于改变了对象hashCode值致使对象没有能从HashSet中删除,从而导致内存泄露。
看下这个例子:
private static class Point {
private int x;
private int y;
Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x &&
y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, 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;
}
}
public static void main(String[] args) {
HashSet<Point> hashSet = new HashSet<>();
Point p1 = new Point(3, 3);
Point p2 = new Point(3, 5);
hashSet.add(p1);
hashSet.add(p2);
p2.setY(7);
hashSet.remove(p2);
System.out.println(hashSet.size());
}
你可能认为打印结果是1,但是运行后结果是2。
当一个对象被存储在HashSet中后,如果修改参与计算hashCode有关的字段,那么修改后的hashCode的值就与一开始存储进来的hashCode的值不同了,这样contains无法通过hashCode找到该元素,所以无法删除。这就告诉我们,当一个对象被存储在HashSet中后,不要修改与计算hashCode有关的字段。
---------------------
【转载,仅作分享,侵删】
作者:况众文
原文:https://blog.csdn.net/u014294681/article/details/85344555
版权声明:本文为博主原创文章,转载请附上博文链接!
|
|