public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
这是Object类的equals和hashcode方法
hashcode是native修饰也就是C来实现的,其实就是取对象在内存里的地址
equals采用了’==’操作,我们都知道这是针对对象地址进行比较。那么这里天然满足:
- equals=true,hashcode一定相等,因为地址都一样,而hashcode取的就是地址。同理:
- equals=false,hashcode一定不相等。
扩展:那为什么基本类型和字符串(也不一定)可以比实际值呢?是因为java内部对==运算符进行了重载,但这个功能没有开放出来,比如C++就提供了对运算符进行重载的能力。
你会问:我为什么要重写equals?_
很显然,地址相等就是指向同一个对象,那么两个句柄肯定相等,但这个条件太苛刻了,实际开发中,很多情况就是两个堆上分配的不同对象进行比较,比如一个Stu类,有一个学号sid,我们业务上认为sid相同,这两个对象就是相等的,那么可以重写equals,因为是基于业务条件来判断相等,那么会出现:
- equals=true/fasle,hashcode都不相等。因为堆上地址不相等。
扩展:实际工作中真的碰到这种情况,我们也不会这么做,而是直接比较两个对象的sid,一般sid都是数字或者字符串,根据上面的重载理论就可以天然支持,那么可以省去重写equals的工作。
你会问:hashcode不相等就不相等呗,我又没用到hashcode?
_
你说的没错,你不重写hashcode,jre既不会在编译,也不会在运行时抛异常,但你需要确保:
- 确实没有用到,不只是没有显示用到,包括被用于以哈希表为基础的jdk提供的集合框架的key,比如hashmap、hashset、linkedhashmap等等。
- 所有地方没有用到,你自己没用到,其他一起开发的人没用到,打出去的jar包别人工程里也没用到。
扩展:实际工作中真的碰到这种情况,99%以上的情况不会用自定义类作为key。至少我目前都没见过。90%以上都是用的string,其他可能用的各个基本类型的封装类。因为他们都已经对__equals和hashcode做了优化。
_
与其这么麻烦,还不如重写呢是不是!重写简单吗,说简单也不简单。要满足这种规则:
- equals=true,hashcode一定要相等。
- equals=false,hashcode尽量保证不相等。
为什么是尽量保证不相等,而不是一定要求不相等。因为做不到,这要从哈希算法角度来讲,稍微了解hash算法的同学都知道,hash算法是一个摘要算法,也就是输入任意一个内容,输出去一个int值。int值最大是多少:2 ^ 31 - 1大概20多亿吧。但是现实世界中的内容是无穷无尽的,假设对有30亿个内容输入,你告诉我怎么办?所以哈希冲突不可避免。
扩展:从另一个角度来理解,hash是一个无限集合到有限集合的映射关系,所以一定会存在溢出或冲突的问题。
你会问:为什么要去尽量保证hashcode不等?
还是那句话,你要是没用于哈希表,连重写都不用,更别说保证。
假设你要用于哈希表,那么我们要来先聊聊哈希表,也叫散列表。是一种常用的数据结构,详见哈希表。
你可以找到一个关键语句:哈希表可以提高查找效率。
基于这个前提,如果不保证不等,最差情况比如直接返回一个常数,也就是所有内容的hashcode都相等。
哈希冲突怎么办?
- 开放寻址:就是按顺序去寻找下一个(顺序或随机)空位置,适用于稀疏的哈希表,会增加后续冲突的概率。
- 重哈希:因为int就那么大,再来一次最多和当前这个不冲突,但是还是会增加后续冲突的概率。
- 链地址:遇事不决加一层,加一层链表,把冲突的地址链在头结点的后面。
- 建立公共溢出区:遇事不决加一层,加一层数组,冲突都放在数组里。
我们先不管这些冲突解决方案的实现思想,我直接给出结论:不管哪一种方案都会降低哈希表的查找性能。
所以说要尽量保证hashcode不等。
你会问:为什么重写equals一定要重写hashcode?
因为默认的equals是地址相等才为true,也就是最严苛的情况,而此时hashcode也一定能保证相等。
所以任何对equals的重写都只会导致条件变宽,如果只对equals重写而不重写hashcode,可就可能出现:equals=true但hashcode不等的情况。
在这种情况下,如果作为hashmap的key,就会出现两个equals=true的key却都可以put到hashmap中。而且也不能通过get获取到值。列子如下:
static class UserKey{
private String uid;
private String name;
public UserKey(String uid, String name) {
this.uid = uid;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
UserKey userKey = (UserKey) o;
return Objects.equals(uid, userKey.uid) &&
Objects.equals(name, userKey.name);
}
@Override
public int hashCode() {
return Objects.hash(uid, name);
}
}
public static void main(String[] args) {
HashMap<UserKey, String> map = new HashMap<>();
map.put(new UserKey("111", "zl"), "test_value_1");
map.put(new UserKey("111", "zl"), "test_value_2");
System.out.println(map.size());
System.out.println(map.get(new UserKey("111", "zl")));
}
// 如果不重写hashcode,返回如下:
2
null
// 如果重写hashcode,返回如下:
1
test_value_2
以上です!!!**