前言
大家好啊,我是汤圆,明天给大家带来的是《Java中的equals()和hashCode() - 具体篇》,心愿对大家有帮忙,谢谢
文章纯属原创,集体总结不免有过错,如果有,麻烦在评论区回复或后盾私信,谢啦
简介
说到equals和hashCode,首先要说下Object
咱们都晓得,这个Object是Java所有类的超类,其余类都是从Object间接或间接继承而来的
而Object中自带的equals和hashCode办法就是明天咱们要议论的话题
目录
- 什么是equals()办法
- 什么是hashCode()办法
- equals和hashCode有啥关系
等等
注释
PS:注释可能比拟长,有点像是一层层在剥洋葱,所以会显得有点啰嗦,须要看论断的能够间接跳到文末看总结
什么是equals办法
equals办法用来比拟两个对象的属性是否相等,也能够说是比拟两个援用对象是否为同一个对象(因为Object中的equals就是这个意
思,如果你没有覆写equals办法,那么就能够这么说)
因为在Object中没有属性,所以就只比拟了两个援用指向的对象是否相等
只有对象不相等,那么就返回false(其实这样对子类来说是很不敌对的,太相对了,请往下看)
代码如下所示:
public class Object { public boolean equals(Object obj) { // 能够看到,官网括号的写法很标准(向老人家学习) return (this == obj); }}
然而咱们平时在定义类时,都或多或少会蕴含几个属性
比方上面的例子
public class EqualsDemo { private int m; // 省略 getter,setter,constructor(m) public static void main(String[] args) { EqualsDemo demo1 = new EqualsDemo(1); EqualsDemo demo2 = new EqualsDemo(1); // 这里冀望返回true,理论却是false System.out.println(demo1.equals(demo2)); } // 这里连续Object的写法,只单纯地比拟两个援用指向的对象是否相等 @Override public boolean equals(Object o) { return this == o; }}
其中定义了一个根本类型的属性 int m;
而后两个实例 demo1 和 demo2 都领有雷同的属性 m = 1;
然而equals办法却返回false
起因就是,equals办法没有正确地编写
equals怎么才算正确编写呢?
咱们应该把属性也进行比对,而不是单纯地比拟对象的援用
(这就好比咱们选一半,不能只看外在,而是要外在内在一起看,那样就。。。就都单着吧)
修改后的代码如下所示:
public class EqualsDemo { private int m; // 省略 getter,setter,constructor(m) public static void main(String[] args) { EqualsDemo demo1 = new EqualsDemo(1); EqualsDemo demo2 = new EqualsDemo(1); // 这时就会返回true System.out.println(demo1.equals(demo2)); } @Override public boolean equals(Object o) { if (this == o) return true; // 加了上面这两行,对属性进了比对 EqualsDemo that = (EqualsDemo) o; return m == that.m; }}
下面看起来如同没什么问题了,然而理论运行却很容易呈现空指针异样或者类型转换异样
因为equals办法中,咱们在强转之前没有对参数 o 进行查看
查看什么呢?
查看两个中央:
- 首先要确保o不能为空null
- 其次确保o是EqualsDemo类或者子类(父类行不行?不行,父类没有子类特有的属性,强转还是会报错)
代码如下:
public class EqualsDemo { private int m; // 省略 getter,setter,constructor(m) public static void main(String[] args) { EqualsDemo demo1 = new EqualsDemo(1); EqualsDemo demo2 = new EqualsDemo(1); System.out.println(demo1.equals(demo2)); } @Override public boolean equals(Object o) { if (this == o) return true; // 加了这一行判断 if (!(o instanceof EqualsDemo)) return false; EqualsDemo that = (EqualsDemo) o; return m == that.m; }}
下面用到了instanceof来判断(别,我晓得你要说啥,敌人咱先往下看)
instanceof的用法是 A instanceof B,用来判断A是否为B类或者B的子类
这样就能够避免空指针和转换异样的呈现
所以equals判断的内容总结下来就是三步:
- 判断两个援用指向的对象是否相等
- 判断传来的参数是否为以后类或者以后类的子类
- 比拟各个属性值是否相等
如果属性是对象的援用,那第三步该怎么比呢?
那就有点像套娃了(什么?没听过套娃?强烈推荐你去看陈翔六点半,外面有很多套娃的案例【您的账户已到账0.5毛】)
比方上面的代码
public class EqualsDemo { private int m; private String str; public static void main(String[] args) { EqualsDemo demo1 = new EqualsDemo(1, "JavaLover1"); EqualsDemo demo2 = new EqualsDemo(1, "JavaLover1"); System.out.println(demo1.equals(demo2)); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EqualsDemo)) return false; EqualsDemo demo = (EqualsDemo) o; // 改了这行 return m == demo.m && str.equals(demo.str); }}
能够看到,多了一个String对象援用作为属性
那咱们在比拟的时候,依据套娃的准则,再次利用String对象的equals办法进行比拟即可
其余的局部都一样
好了,当初equals办法写完了,我上个厕所先
真的写完了吗?我不信(脑补ing。。。)
因为还是有潜在的空指针异样
构想一下,下面str真的会存在吗?如果str为null怎么办?
所以咱们还要对str进行空指针判断,不过不须要咱们来做,而是通过Objects这个工具类(Java7诞生的一个工具类),它内置的equals
办法能够帮你在比拟两个对象的同时加上null判断
Objects.equals办法如下:
public final class Objects { public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }}
改了当前的equals()最终代码如下:
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EqualsDemo)) return false; EqualsDemo demo = (EqualsDemo) o; return m == demo.m && Objects.equals(str,demo.str); }
好了,万事俱备了,只欠东风
东风?什么东风?
东风就是你的父亲啊
如果是在子类中定义equals,那么还要思考到父类(如果间接继承自Object则不思考)
改了之后的代码如下:
@Overridepublic boolean equals(Object o) { if (this == o) return true; if (!(o instanceof EqualsDemo)) return false; // 加了这一行 if(!super.equals(o)) return false; EqualsDemo demo = (EqualsDemo) o; return m == demo.m && Objects.equals(str,demo.str); }
你可能想晓得,为啥放到第三行?
那是因为前两行属于最外侧的判断
你能够这样想,如果传来的对象o是父类的对象,那么父类super的判断放在这个地位就很适合了
(因为此时o instanceof EqualsDemo
必定返回false,这样就省去了super.equals()的判断)
好了,我累了,能够完结了吗?
等一下,地球还没有覆灭,事件也还没有完结。
下面的instanceof有个很大的缺点,就是违反了equals的对称性
上面咱们顺藤摸瓜,来说下equals办法标准的5个个性:
- 自反性:就是本人反过来跟本人比,要返回true;比方x.equals(x) == true
- 对称性:就是x.equals(y) == true时,也要y.equals(y) == true
- 传递性:就是x.equals(y) == true,同时y.equals(z) == true,那么x.equals(z) == true
- 一致性:就是传说中的幂等性,即x.equals(y)调用屡次,都应该返回一样的后果
- 非空和空比拟则返回false的个性:就是x.equals(y)中,如果x非空,而y空,则返回false
好了,回到instanceof,下面提到它没有满足对称性
是因为用了instanceof来做比拟的话,Son.equals(Father)永远不会为真,而Father.equals(Son)却有可能为真,这就不对称了
所以罗唆就让Father.equals(Son)也永远不为真
那要怎么做呢?
答案就是instanceof的弟弟:getClass
instanceof
用来判断是否为以后类或者子类
而getClass
只用来判断是否为以后类
改了之后,代码如下
public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if(!super.equals(o)) return false; EqualsDemo demo = (EqualsDemo) o; return m == demo.m && Objects.equals(str,demo.str); }
好了,看敌人们也累了,明天的equals办法就先到这里了
上面总结下equals办法的核心内容,总共就是五步(这次真的是完结了):
- 判断两个援用指向的对象是否相等
- 判断传来的参数是否为空
- 判断传来的参数是否属于以后类
- 如果有继承父类,则也须要调用父类的super.equals()办法(Object除外)
- 最初比拟各个属性值是否相等(如果属性为对象援用,则须要通过Objects.equals(a,b)办法来比拟援用对象的属性值)
什么是hashCode()办法
hashCode也叫散列码(哈希码),它用来计算对象中所有属性的散列值
对于散列这里就不开展了,咱们在这里只须要晓得两点:
- 散列值为整数,能够为负值
- 散列值能够用来确定元素在散列表中的地位(有可能两个元素领有雷同的散列值,这个就是散列抵触)
在Object中,hashCode()是一个本地办法,因为Object没有属性,所以默认返回的是对象的内存地址
代码如下所示:
public class Test2 { public static void main(String[] args) { Object t = new Object(); int a = t.hashCode(); System.out.println(Integer.toHexString(a)); // 输入 4554617c }}
其中 4554617c
就是对象a的内存地址,这里转成16进制显示(是因为通常地址都是用16进制显示的,比方咱们电脑的Mac地址)
上面总结下hashCode
的几个个性:
- 一致性:无论hashCode调用多少次,都应该返回一样的后果(这一点跟equals很像)
- 追随性(本人编的一个性):如果两个对象的equals返回为真,那么hashCode也应该相等
- 反过来,如果两个对象的equals返回为假,那么hashCode有可能相等,然而如果散列的足够好,那么通常来说hashCode()也不应该相等
- 覆写equals办法时,肯定要覆写hashCode办法
equals和hashCode有什么分割呢?
hashCode和equals能够说相辅相成的,他俩独特合作用来判断两个对象是否相等
如果离开来看的话,他俩是没什么分割的,然而因为某些起因导致被分割上了(比方HashMap这个小月老)
上面来细说一下
咱们晓得 HashMap汇合中的key是不能反复的,那它是怎么判断反复的呢?
就是通过equals和hashCode来判断的
上面是局部源码
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e;
能够看到,map先进行hash判断,而后进行equals判断
也就是说,hash是前提,如果hash都不相等,那equals就不必比拟了(先计算hash的一个起因是计算hash比equals快得多)
所以咱们在自定义对象时,如果覆写了equals,那么肯定要记得覆写hashCode,(当然,假如这里的自定义对象是用来作为map中的key键的)
覆写代码如下:
@Override public boolean equals(Object o) { if (this == o) return true; if(getClass() != o.getClass()) return false; if(!super.equals(o)) return false; EqualsDemo demo = (EqualsDemo) o; return m == demo.m && Objects.equals(str,demo.str); } @Override public int hashCode() { return Objects.hash(m, str); }
其中Objects.hash有点相似于下面的Objects.equals()办法,很实用
如果只覆写了equals,没有覆写hashCode,会咋样呢?
后果就是:
当你创立两个对象(属性统一,然而内存地址不统一),作为key放到map中时就会被当成两个key来寄存
同理可得,获取数据value的时候,也是不统一的
上面是只覆写equals没覆写hashCode的代码:能够看到,两次取到的值是不一样的
public class HashCodeDemo{ public static void main(String[] args) { // 两个对象的属性都为n = 1 HashCodeDemo demo1 = new HashCodeDemo(1); HashCodeDemo demo2 = new HashCodeDemo(1); Map<HashCodeDemo, Integer> map = new HashMap<>(); map.put(demo1, 1); map.put(demo2, 2); System.out.println(map.get(demo1)); // 输入1 System.out.println(map.get(demo2)); // 输入2 } private int n; public HashCodeDemo(int n) { this.n = n; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HashCodeDemo that = (HashCodeDemo) o; return n == that.n; } public int getN() { return n; } public void setN(int n) { this.n = n; }}
同时覆写equals和hashCode的代码:能够看到,两次取到的值都是一样的
public class HashCodeDemo{ public static void main(String[] args) { HashCodeDemo demo1 = new HashCodeDemo(1); HashCodeDemo demo2 = new HashCodeDemo(1); Map<HashCodeDemo, Integer> map = new HashMap<>(); map.put(demo1, 1); // 第二次会笼罩第一次的值,因为key相等(equals和hashCode都相等) map.put(demo2, 2); System.out.println(map.get(demo1)); // 输入2 System.out.println(map.get(demo2)); // 输入2 } private int n; public HashCodeDemo(int n) { this.n = n; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HashCodeDemo that = (HashCodeDemo) o; return n == that.n; } @Override public int hashCode() { return Objects.hash(m, str); } public int getN() { return n; } public void setN(int n) { this.n = n; }}
HashSet汇合也是同理,因为它外部的就是依赖HashMap实现的(这个后面有简略介绍过,感兴趣的能够回顾一下)
总结
- equals办法的个性:
- 自反性:就是本人反过来跟本人比,要返回true;比方x.equals(x) == true
- 对称性:就是x.equals(y) == true时,也要y.equals(y) == true
- 传递性:就是x.equals(y) == true,同时y.equals(z) == true,那么x.equals(z) == true
- 一致性:就是传说中的幂等性,即x.equals(y)调用屡次,都应该返回一样的后果
- 非空和空比拟则返回false的个性:就是x.equals(y)中,如果x非空,而y空,则返回false
- hashCode的个性以及和equals的分割
- 一致性:无论hashCode调用多少次,都应该返回一样的后果(这一点跟equals很像)
- 追随性(本人编的一个性):如果两个对象的equals返回为真,那么hashCode也应该相等
- 反过来,如果两个对象的equals返回为假,那么hashCode有可能相等,然而如果散列的足够好,那么通常来说hashCode()也不应该相等
- 覆写equals办法时,肯定要覆写hashCode办法
后记
最初,感激大家的观看,谢谢