<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們都知道,要比較兩個物件是否相等時需要呼叫物件的equals()方法,即判斷物件參照所指向的物件地址是否相等,物件地址相等時,那麼與物件相關的物件控制程式碼、物件頭、物件範例資料、物件型別資料等也是完全一致的,所以我們可以通過比較物件的地址來判斷是否相等。
物件在不重寫的情況下使用的是Object的equals方法和hashcode方法,從Object類的原始碼我們知道,預設的equals 判斷的是兩個物件的參照指向的是不是同一個物件;而hashcode也是根據物件地址生成一個整數數值;
另外我們可以看到Object的hashcode()方法的修飾符為native,表明該方法是否作業系統實現,java呼叫作業系統底層程式碼獲取雜湊值。
public class Object { public native int hashCode(); /** * Indicates whether some other object is "equal to" this one. * <p> * The {@code equals} method implements an equivalence relation * on non-null object references: * <ul> * <li>It is <i>reflexive</i>: for any non-null reference value * {@code x}, {@code x.equals(x)} should return * {@code true}. * <li>It is <i>symmetric</i>: for any non-null reference values * {@code x} and {@code y}, {@code x.equals(y)} * should return {@code true} if and only if * {@code y.equals(x)} returns {@code true}. * <li>It is <i>transitive</i>: for any non-null reference values * {@code x}, {@code y}, and {@code z}, if * {@code x.equals(y)} returns {@code true} and * {@code y.equals(z)} returns {@code true}, then * {@code x.equals(z)} should return {@code true}. * <li>It is <i>consistent</i>: for any non-null reference values * {@code x} and {@code y}, multiple invocations of * {@code x.equals(y)} consistently return {@code true} * or consistently return {@code false}, provided no * information used in {@code equals} comparisons on the * objects is modified. * <li>For any non-null reference value {@code x}, * {@code x.equals(null)} should return {@code false}. * </ul> * <p> * The {@code equals} method for class {@code Object} implements * the most discriminating possible equivalence relation on objects; * that is, for any non-null reference values {@code x} and * {@code y}, this method returns {@code true} if and only * if {@code x} and {@code y} refer to the same object * ({@code x == y} has the value {@code true}). * <p> * Note that it is generally necessary to override the {@code hashCode} * method whenever this method is overridden, so as to maintain the * general contract for the {@code hashCode} method, which states * that equal objects must have equal hash codes. * * @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */ public boolean equals(Object obj) { return (this == obj); } }
假設現在有很多學生物件,預設情況下,要判斷多個學生物件是否相等,需要根據地址判斷,若物件地址相等,那麼物件的範例資料一定是一樣的,但現在我們規定:當學生的姓名、年齡、性別相等時,認為學生物件是相等的,不一定需要物件地址完全相同,例如學生A物件所在地址為100,學生A的個人資訊為(姓名:A,性別:女,年齡:18,住址:北京軟體路999號,體重:48),學生A物件所在地址為388,學生A的個人資訊為(姓名:A,性別:女,年齡:18,住址:廣州暴富路888號,體重:55),這時候如果不重寫Object的equals方法,那麼返回的一定是false不相等,這個時候就需要我們根據自己的需求重寫equals()方法了。
package jianlejun.study; public class Student { private String name;// 姓名 private String sex;// 性別 private String age;// 年齡 private float weight;// 體重 private String addr;// 地址 // 重寫hashcode方法 @Override public int hashCode() { int result = name.hashCode(); result = 17 * result + sex.hashCode(); result = 17 * result + age.hashCode(); return result; } // 重寫equals方法 @Override public boolean equals(Object obj) { if(!(obj instanceof Student)) { // instanceof 已經處理了obj = null的情況 return false; } Student stuObj = (Student) obj; // 地址相等 if (this == stuObj) { return true; } // 如果兩個物件姓名、年齡、性別相等,我們認為兩個物件相等 if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.age.equals(this.age)) { return true; } else { return false; } } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public float getWeight() { return weight; } public void setWeight(float weight) { this.weight = weight; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } }
現在我們寫個例子測試下結果:
public static void main(String[] args) {
Student s1 =new Student();
s1.setAddr("1111");
s1.setAge("20");
s1.setName("allan");
s1.setSex("male");
s1.setWeight(60f);
Student s2 =new Student();
s2.setAddr("222");
s2.setAge("20");
s2.setName("allan");
s2.setSex("male");
s2.setWeight(70f);
if(s1.equals(s2)) {
System.out.println("s1==s2");
}else {
System.out.println("s1 != s2");
}
}
在重寫了student的equals方法後,這裡會輸出s1 == s2,實現了我們的需求,如果沒有重寫equals方法,那麼上段程式碼必定輸出s1!=s2。
通過上面的例子,你是不是會想,不是說要同時重寫Object的equals方法和hashcode方法嗎?那上面的例子怎麼才只用到equals方法呢,hashcode方法沒有體現出來,不要著急,我們往下看。
以上面例子為基礎,即student1和student2在重寫equals方法後被認為是相等的。
在兩個物件equals的情況下進行把他們分別放入Map和Set中
在上面的程式碼基礎上追加如下程式碼:
Set set = new HashSet(); set.add(s1); set.add(s2); System.out.println(set);
如果沒有重寫Object的hashcode()方法(即去掉上面student類中hashcode方法塊),這裡會輸出
[jianlejun.study.Student@7852e922, jianlejun.study.Student@4e25154f]
說明該Set容器類有2個元素。.........等等,為什麼會有2個元素????剛才經過測試,s1不是已經等於s2了嗎,那按照Set容器的特性會有一個去重操作,那為什麼現在會有2個元素。這就涉及到Set的底層實現問題了,這裡簡單介紹下就是HashSet的底層是通過HashMap實現的,最終比較set容器內元素是否相等是通過比較物件的hashcode來判斷的。現在你可以試試吧剛才註釋掉的hashcode方法弄回去,然後重新執行,看是不是很神奇的就只輸出一個元素了
@Override public int hashCode() { int result = name.hashCode(); result = 17 * result + sex.hashCode(); result = 17 * result + age.hashCode(); return result; }
或許你會有一個疑問?hashcode裡的程式碼該怎麼理解?該如何寫?其實有個相對固定的寫法,先整理出你判斷物件相等的屬性,然後取一個儘可能小的正整數(儘可能小時怕最終得到的結果超出了整型int的取數範圍),這裡我取了17,(好像在JDK原始碼中哪裡看過用的是17),然後計算17*屬性的hashcode+其他屬性的hashcode,重複步驟。
重寫hashcode方法後輸出的結果為:
[jianlejun.study.Student@43c2ce69]
同理,可以測試下放入HashMap中,key為<s1,s1>,<s2,s2>,Map也把兩個同樣的物件當成了不同的Key(Map的Key是不允許重複的,相同Key會覆蓋)那麼沒有重寫的情況下map中也會有2個元素,重寫的情況會最後put進的元素會覆蓋前面的value
Map m = new HashMap(); m.put(s1, s1); m.put(s2, s2); System.out.println(m); System.out.println(((Student)m.get(s1)).getAddr()); 輸出結果: {jianlejun.study.Student@43c2ce69=jianlejun.study.Student@43c2ce69} 222
可以看到最終輸出的地址資訊為222,222是s2成員變數addr的值,很明天,s2已經替換了map中key為s1的value值,最終的結果是map<s1,s2>。即key為s1value為s2.
因為我們沒有重寫父類別(Object)的hashcode方法,Object的hashcode方法會根據兩個物件的地址生成對相應的hashcode;
s1和s2是分別new出來的,那麼他們的地址肯定是不一樣的,自然hashcode值也會不一樣。
Set區別物件是不是唯一的標準是,兩個物件hashcode是不是一樣,再判定兩個物件是否equals;
Map 是先根據Key值的hashcode分配和獲取物件儲存陣列下標的,然後再根據equals區分唯一值(詳見下面的map分析)
案例:
(1)hashmap儲存
存值規則:把Key的hashCode 與HashMap的容量 取餘得出該Key儲存在陣列所在位置的下標(原始碼定位Key儲存在陣列的哪個位置是以hashCode & (HashMap容量-1)演演算法得出)這裡為方便理解使用此方式;
//為了演示方便定義一個容量大小為3的hashMap(其預設為16)
HashMap map=newHashMap(3);
map.put("a",1); 得到key 為“a” 的hashcode 值為97然後根據 該值和hashMap 容量取餘97%3得到儲存位到陣列下標為1;
map.put("b",2); 得到key 為“b” 的hashcode 值為98,98%3到儲存位到陣列下標為2;
map.put("c",3); 得到key 為“c” 的hashcode 值為99,99%3到儲存位到陣列下標為0;
map.put("d",4); 得到key 為“d” 的hashcode 值為100,100%3到儲存位到陣列下標為1;
map.put("e",5); 得到key 為“e” 的hashcode 值為101,101%3到儲存位到陣列下標為2;
map.put("f",6); 得到key 為“f” 的hashcode 值為102,102%3到儲存位到陣列下標為0;
(2)hashmap的查詢key
得到key在陣列中的位置:根據上圖,當我們獲取key 為“a”的物件時,那麼我們首先獲得 key的hashcode97%3得到儲存位到陣列下標為1;
匹配得到對應key值物件:得到陣列下表為1的資料“a”和“c”物件, 然後再根據 key.equals()來匹配獲取對應key的資料物件;
hashcode 對於HashMapde:如果沒有hashcode 就意味著HashMap儲存的時候是沒有規律可尋的,那麼每當我們map.get()方法的時候,就要把map裡面的物件一一拿出來進行equals匹配,這樣效率是不是會超級慢;
在equals方法沒被修改的前提下,多次呼叫同一物件的hashcode方法返回的值必須是相同的整數;
如果兩個物件互相equals,那麼這兩個物件的hashcode值必須相等;
為不同物件生成不同的hashcode可以提升雜湊表的效能;
經常能看到重寫equals方法就需要重寫hashCode方法的說法,這點也很好理解,假如重寫equals使得兩個物件通過equals判斷為真 ,但是如果hashCode計算出來的值如果不一樣,就會發生矛盾,就是明明兩個物件是一樣的,但是卻會被對映到不同位置,這樣子的話,hashMap或者hashSet之類的雜湊結構就會儲存多個相同的物件。
Map<String,Value> map1 = new HashMap<String,Value>(); String s1 = new String("key"); String s2 = new String("key"); Value value = new Value(2); map1.put(s1, value); System.out.println("s1.equals(s2):"+s1.equals(s2)); System.out.println("map1.get(s1):"+map1.get(s1)); System.out.println("map1.get(s2):"+map1.get(s2)); Map<Key,Value> map2 = new HashMap<Key,Value>(); Key k1 = new Key("A"); Key k2 = new Key("A"); map2.put(k1, value); System.out.println("k1.equals(k2):"+k1.equals(k2)); System.out.println("map2.get(k1):"+map2.get(k1)); System.out.println("map2.get(k2):"+map2.get(k2));
Key和Value的類定義如下
static class Key{ private String k; public Key(String key){ this.k=key; } //如果不重寫hashCode,只重寫了equals,會造成相同值被放入不同的桶中 // @Override // public int hashCode() { // return k.hashCode(); // } @Override public boolean equals(Object obj) { if(obj instanceof Key){ Key key=(Key)obj; return k.equals(key.k); } return false; } } static class Value{ private int v; public Value(int v){ this.v=v; } @Override public String toString() { return "類Value的值-->"+v; } }
輸出結果如下
可以看出,如果重寫了equals但不重寫hashCode的話,會出現相同的物件會被map判斷成不同物件,導致可以重複插入多個相同物件。
除此之外,還會思考如果重寫hashCode但不重寫equals方法的情況下,又會造成什麼問題,因此用以下例子說明
Map<Integer, Integer> map3 =new HashMap(); while (true){ boolean flag = false; for (int i = 0; i < 1000; i++) { if(!map3.containsKey(i)){ map3.put(i, i); flag = true; } } if (flag == false) { break; } System.out.println("map3的容量" + map3.size()); } Map<Key2, Integer> map4 =new HashMap(); while (true){ boolean flag = false; for (int i = 0; i < 1000; i++) { if(!map4.containsKey(new Key2(i))){ map4.put(new Key2(i), i); flag = true; } } if (flag == false) { break; } System.out.println("map4的容量" + map4.size()); }
Key2的類定義如下
static class Key2{ Integer id; Key2(Integer id) { this.id = id; } @Override public int hashCode() { return id.hashCode(); } //不重寫equals就會導致一直認為沒有相同的值,就會一直插入。 // @Override // public boolean equals(Object obj) { // if(obj instanceof Key2){ // Key2 key2 =(Key2)obj; // return id.equals(key2.id); // } // return false; // } }
結果如下
從圖中結果可以看出,map4一直在新增資料,說明map一直認為沒有相同的key物件,因此對於同一個i,不重寫的equals永遠不會判斷相同,所以會一直插入。因此hashCode和equals必須全部重寫,任何一個不重寫都會發生錯誤。
到這裡也還會思考,String和Integer的和hashCode和equals是怎麼計算的
Integer的hashCode計算如下
可以看出是直接返回原始值
String的hashCode計算如下
可以由註釋看出來,計算的結果就是s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],就比如字串“abc”,a的ascll碼是97,b是98,c是99,因此該字串的hashCode值就是(97 *31 + 98)*31 + 99,這裡引出一點思考:為什麼用31呢?,查閱資料得知因為31是一個質數,可以使得減少雜湊演演算法的衝突概率,同時31的二進位制數是11111,因此31 *i就等於(i << 5) - i,可以優化運算。
Integer的equals計算如下
就是說明,只需要使用instanceof判斷傳入物件是否是Integer的範例或者子類,是就強轉成Integer類,然後判斷值是否相等
String的equals計算如下
這裡首先用“==”比較了equals兩邊物件,如果一樣直接返回true,然後就是用instanceof判斷是否是String範例或者子類,如果是就強轉,然後再根據陣列長度判斷是否相同,如果相同就遍歷陣列每個元素,都相同就返回true,其他情況都返回false。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45