为什么重写equals()方法就必须重写hashCode()方法呢? 逃离我推掉我的手 2022-10-05 09:51 115阅读 0赞 ### 文章目录 ### * 前言 * 一、equals和== * 二、hashCode()方法 * 三、hashCode() 与 equals() * * 1.不会创建“类对应的散列表”,不存在重写equals()要重写hashCode() * 2.当类需要放在HashTable、HashMap、HashSet等hash结构的集合时需要重载hashCode() * 总结 -------------------- # 前言 # 对于Java开发或者Android开发的小伙伴来说,在面试的时候,应该都会遇到面试官问这么一个问题:你知道equals()和 == 的区别在哪嘛?小伙伴的第一反应一般都会是回答:equals()比较的是内容是不是相同,如想相同的话返回true,否者返回false,“==”的则比较的内存空间地址是否相同,这样的回答的确是没问题的,但是面试官反手接着就问,那你知道为什么重写equals()方法就必须重写hashCode()方法嘛?这可能会难倒很多小伙伴吧,不慌,当你读完这篇文章 ,你就会知道为什么重写equals()方法就必须重写hashCode()方法了; -------------------- # 一、equals和== # 我们先来聊聊equals和== * ==:它的作用主要是用来判断两个对象的地址是不是相同的,也就是判断两个对象是不是同一个对象; * equals:对于equals来说,它这边会分两种情况,一种是这个类没有覆盖equals()方法,那么它的作用效果是跟“==”是一样的,另外一种是类覆盖了equals(),那么这种情况就是比较两个对象的内容是不是相同的,举个例子: String s1 = new String("Hello"); String s2 = new String("Hello"); String s3 = "Hello"; Log.d(TAG, "s1 == s2: " + (s1 == s2)); Log.d(TAG, "s1.equals(s2): " + s1.equals(s2)); Log.d(TAG, "s1.equals(s3: " + s1.equals(s3)); 大家想想这三个的输出结果回事什么呢?想必大家都知道吧 s1 == s2: false s1.equals(s2): true s1.equals(s3: true s1==s2返回false是因为它们各自创建了一个实例,内存地址是不相同的,所以会返回false,而s1.equals(s2)上面也说过比较的是内容,内容相同就会返回true,那这里就会有人问了,你上面不是说equals()没有被覆盖的情况下比较的是对象的地址嘛,也没看到你覆盖掉equals()方法呀?这是因为equals()是Object中的一个方法,它是在String这个类里面被覆盖了,也就是String类中的equals重写了Object中的equals方法,使其比较的是字符的内容,而不是引用地址,所以我们一般String类的equals()是用来比较这两个内容是否相同的,这里贴一下String的equals源码,大家可以看看: /** * Compares this string to the specified object. The result is {@code * true} if and only if the argument is not {@code null} and is a {@code * String} object that represents the same sequence of characters as this * object. * * @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(String) */ public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = length(); if (n == anotherString.length()) { int i = 0; while (n-- != 0) { if (charAt(i) != anotherString.charAt(i)) return false; i++; } return true; } } return false; } # 二、hashCode()方法 # hashCode()原理是通过获取到的哈希吗来确定这个对象在哈希表中的索引位置,哈希码也被称为散列码,实际上就是一串数字 Object obj = new Object(); Log.d(TAG, "hashCode: " +obj.hashCode()); 输出的结果为:148642134,证实了上述说法; 举个栗子: String s1 = "hello"; String s2 = new String("hello"); Log.d(TAG, "s1.hashCode(): " + s1.hashCode()); Log.d(TAG, "s2.hashCode(): " + s2.hashCode()); 大家想想这两个的哈希吗会不会相同呢? ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyODg5NDc2_size_16_color_FFFFFF_t_70] 结果是相同的哦,因为对于字符串对象来说是根据字符串的内容来生成hashCode的,因为这里的s1和s2内容都是一样的,所以得出的哈希码也会是相同的,如果两个对象通过equals()来比较结果是相同的话,那么他们的hashCode也会是一样的,这也就验证了为什么说两个对象调用了equals()结果是true的时候他们的hashCode一定也是相同的; 再举个栗子:如果有一个集合,我需要往里面添加元素,但是前提是保证这个集合里面的元素不能出现有重复的,那这里小伙伴的第一反应应该会是equals(),在添加元素之前,用equals()方法去对比集合里面已有的元素,返回false的话就添加,如果元素少的话这个方法是可行的,但是如果集合里面已经有1000个元素了呢,难不成要调用1000次equals()方法嘛,这样效率会大大降低的,这个时候就有了hashCode(),当集合要添加新的元素时,将对象通过哈希算法计算得到哈希值(正整数),然后将哈希值和集合(数组)长度进行&运算,得到该对象在该数组存放的位置索引。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,保持原有的元素,不相同就表示发生冲突了,散列表对于冲突有具体的解决办法,但最终还会将新元素保存在适当的位置; # 三、hashCode() 与 equals() # 圆规正传,接下来我们就聊聊为什么重写equals()方法就必须重写hashCode()方法?hashCode() 和 equals() 这两个方法有什么关系?这里也是分两种情况来说明这个问题 ## 1.不会创建“类对应的散列表”,不存在重写equals()要重写hashCode() ## 这里我们先来说说什么是不会创建“类对应的散列表”,顾名思义就是说我们不会在HashSet、HashTable、HashMap等散列表的数据结构中使用到该类,在这种情况下,重不重写hashCode()方法与equals()方法是没有联系的,为什么这么说呢,看完下面这个栗子你就会知道了: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Person p1 = new Person("Hello",111); Person p2 = new Person("Hello",111); Person p3 = new Person("Hello World",222); Log.d(TAG, " p1.equals(p2): " + p1.equals(p2) + ",p1.hashCode():" + p1.hashCode() + ", p2.hashCode():" + p2.hashCode()); Log.d(TAG, " p1.equals(p3): " + p1.equals(p3) + ",p1.hashCode():" + p1.hashCode() + ", p3.hashCode():" + p3.hashCode()); } private static class Person{ private String str; private int i; public Person(String str, int i) { this.str = str; this.i = i; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return i == person.i && Objects.equals(str, person.str); } } 输出结果为: > p1.equals(p2): true,p1.hashCode():148642134, p2.hashCode():66268631 > p1.equals(p3): false,p1.hashCode():148642134, p3.hashCode():30160580 对于当我们不在HashSet, HashTable, HashMap等等这些本质是散列表的数据结构中用到这个类作为泛型,此时这个类的hashCode() 和 equals()没有任何关系,在p1和p2使用equals()比较相等的情况下,hashCode()也不一定相等,一般的地方是不需要重载hashCode的,那什么时候需要重写呢? ## 2.当类需要放在HashTable、HashMap、HashSet等hash结构的集合时需要重载hashCode() ## 一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,为什么这里就需要重载hashCode()呢,等看完下面栗子我会告诉大家这里为什么一定需要重写hashCode(); 这个栗子主要是想在一个集合里面添加不相同的元素,只要是相同的就不添加 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Person p1 = new Person("Hello",111); Person p2 = new Person("Hello",111); Person p3 = new Person("Hello World",222); // 新建HashSet对象 HashSet<Person> hashSet = new HashSet<>(); // 把三个元素添加进集合里面 hashSet.add(p1); hashSet.add(p2); hashSet.add(p3); Log.d(TAG, " p1.equals(p2): " + p1.equals(p2) + ",p1.hashCode():" + p1.hashCode() + ", p2.hashCode():" + p2.hashCode()); Log.d(TAG, " p1.equals(p3): " + p1.equals(p3) + ",p1.hashCode():" + p1.hashCode() + ", p3.hashCode():" + p3.hashCode()); Log.d(TAG, " hashSet: " + hashSet); } private static class Person{ private String str; private int i; public Person(String str, int i) { this.str = str; this.i = i; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return i == person.i && Objects.equals(str, person.str); } // @Override // public int hashCode() { // return Objects.hash(str, i); // } } > 输出结果: > p1.equals(p2): true,p1.hashCode():148642134, p2.hashCode():66268631 > p1.equals(p3): false,p1.hashCode():148642134, p3.hashCode():30160580 > hashSet: \[com.example.demo.MainActivity $ Person@8dc1956 , com.example.demo.MainActivity$Person@1cc36c4, com.example.demo.MainActivity $Person@3f32dd7\] 这个栗子里面我重写了equals(),没有重写hashCode(),大家可以看到p1和p2应该是重复的元素,不应该添加两个元素到这个集合里面,要么添加p1,不添加p2;要么就添加p2,不添加p1,如果按照我上面的需求(不添加重复的元素),这里的hashSet打印出来的日志应该是两个元素,而不应该是三个元素,这是因为什么原因导致这个集合里面会有重复的元素呢; 相信大家阅读完上面的内容之后心里应该有答案了吧,对,这是因为虽然p1和p2的内容是相等的,但是没有重写hashCode的情况下他们的hashCode值是不相同的,HashSet在添加的时候会认定这两个元素是不相同的,所以hashSet才会打印出三个元素,但是如果我们改一下代码,重写一下hashCode: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Person p1 = new Person("Hello",111); Person p2 = new Person("Hello",111); Person p3 = new Person("Hello World",222); // 新建HashSet对象 HashSet<Person> hashSet = new HashSet<>(); // 把三个元素添加进集合里面 hashSet.add(p1); hashSet.add(p2); hashSet.add(p3); Log.d(TAG, " p1.equals(p2): " + p1.equals(p2) + ",p1.hashCode():" + p1.hashCode() + ", p2.hashCode():" + p2.hashCode()); Log.d(TAG, " p1.equals(p3): " + p1.equals(p3) + ",p1.hashCode():" + p1.hashCode() + ", p3.hashCode():" + p3.hashCode()); Log.d(TAG, " hashSet: " + hashSet); } private static class Person{ private String str; private int i; public Person(String str, int i) { this.str = str; this.i = i; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return i == person.i && Objects.equals(str, person.str); } @Override public int hashCode() { return Objects.hash(str, i); } } > 输出结果: > p1.equals(p2): true,p1.hashCode():-2137067074, p2.hashCode():-2137067074 > p1.equals(p3): false,p1.hashCode():-2137067074, p3.hashCode():-969098597 > hashSet: \[com.example.demo.MainActivity$ Person@809ef1be, com.example.demo.MainActivity$Person@c63cba9b\] 大家看到这里重写了HashCode()方法之后,p1和p2的哈希值就相同了,所以HashSet在添加元素的时候会认定他们两是同一个元素,就不会重复添加,所以最后的打印日志只输出两个元素,满足上述提的需求。 为什么equals()相等,hashCode就一定要相等,而hashCode相等,却不要求equals相等? 对于这个问题,在我的理解内,是只有在使用到HashTable、HashMap、HashSet等的时候会要求equals()相等,hashCode就一定要相等,这样可以避免出一些问题,但是在我们平时开发过程中的话,如果有重写equals()的话,最好也重写hashCode(),这能保证你的代码不会出现问题;之所以hashCode相等,却可以equal不等,就比如ObjectA和ObjectB两个对象他们都有属性name,那么hashCode都以name计算,hashCode一样,但是两个对象属于不同类型,所以equals为false; # 总结 # 1、重写equals方法时需要重写hashCode方法,主要是针对Map、Set等集合类型的使用; a:Map、Set等集合类型存放的对象必须是唯一的; b:集合类判断两个对象是否相等,是先判断equals是否相等,如果equals返回TRUE,还要再判断HashCode返回值是否ture,只有两者都返回ture,才认为该两个对象是相等的; 2、由于Object的hashCode返回的是对象的hash值,所以即使equals返回TRUE,集合也可能判定两个对象不等,所以必须重写hashCode方法,以保证当equals返回TRUE时,hashCode也返回Ture,这样才能使得集合中存放的对象唯一。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyODg5NDc2_size_16_color_FFFFFF_t_70]: /images/20221005/4f3c9a7927a64e86959cb90fc6e7340e.png
还没有评论,来说两句吧...