Java中的set是一個不包含重複元素的集合,確切地說,是不包含e1.equals(e2)的元素對。Set中允許添加null。Set不能保證集合裡元素的順序。
在往set中添加元素時,如果指定元素不存在,則添加成功。也就是說,如果set中不存在(e==null ? e1==null : e.queals(e1))的元素e1,則e1能添加到set中。
下面以set的一個實作類別HashSet為例,簡單介紹一下set不重複實現的原理:
[java] view plain copy print ? package com.darren.test.overide; public class CustomString { private String value; public CustomString() { this(“”); } public CustomString(String value) { this.value = value; } }
package com.darren.test.overide;public class CustomString { private String value; public CustomString() { this(""); } public CustomString(String value) { this.value = value; }}
[java] view plain copy print ? package com.darren.test.overide; import java.util.HashSet; import java.util.Set; public class HashSetTest { public static void main(String[] args) { String a = new String(“A”); String b = new String(“A”); CustomString c = new CustomString(“B”); CustomString d = new CustomString(“B”); System.out.println(”a.equals(b) == ” + a.equals(b)); System.out.println(”c.equals(d) == ” + c.equals(d)); Set<Object> set = new HashSet<Object>(); set.add(a); set.add(b); set.add(c); set.add(d); System.out.println(”set.size() == ” + set.size()); for (Object object : set) { System.out.println(object); } } }
package com.darren.test.overide;import java.util.HashSet;import java.util.Set;public class HashSetTest { public static void main(String[] args) { String a = new String("A"); String b = new String("A"); CustomString c = new CustomString("B"); CustomString d = new CustomString("B"); System.out.println("a.equals(b) == " + a.equals(b)); System.out.println("c.equals(d) == " + c.equals(d)); Set<Object> set = new HashSet<Object>(); set.add(a); set.add(b); set.add(c); set.add(d); System.out.println("set.size() == " + set.size()); for (Object object : set) { System.out.println(object); } }}
運行結果如下:
[plain] view plain copy print ? a.equals(b) == true c.equals(d) == false set.size() == 3 com.darren.test.overide.CustomString@2c39d2 A com.darren.test.overide.CustomString@5795ce
a.equals(b) == truec.equals(d) == falseset.size() == 3com.darren.test.overide.CustomString@2c39d2Acom.darren.test.overide.CustomString@5795ce
也許你已經看出關鍵來了,沒錯就是equals方法。這麼說還是不恰當,準確的說應該是equals和hashcode方法。為什麼這麼說呢,讓我們改一改CustomString類在進行測試:
[java] view plain copy print ? package com.darren.test.overide; public class CustomString { private String value; public CustomString() { this(“”); } public CustomString(String value) { this.value = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CustomString) { CustomString customString = (CustomString) obj; return customString.value.equals(value); } else { return false; } } }
package com.darren.test.overide;public class CustomString { private String value; public CustomString() { this(""); } public CustomString(String value) { this.value = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CustomString) { CustomString customString = (CustomString) obj; return customString.value.equals(value); } else { return false; } }}
測試結果:
[html] view plain copy print ? a.equals(b) == true c.equals(d) == true set.size() == 3 com.darren.test.overide.CustomString@12504e0 A com.darren.test.overide.CustomString@1630eb6
a.equals(b) == truec.equals(d) == trueset.size() == 3com.darren.test.overide.CustomString@12504e0Acom.darren.test.overide.CustomString@1630eb6
這次的equals傳回值都為true,但是set的size還是3
讓我們繼續改
[java] view plain copy print ? package com.darren.test.overide; public class CustomString { private String value; public CustomString() { this(“”); } public CustomString(String value) { this.value = value; } @Override public int hashCode() { // return super.hashCode(); return 1; } }
package com.darren.test.overide;public class CustomString { private String value; public CustomString() { this(""); } public CustomString(String value) { this.value = value; } @Override public int hashCode() { // return super.hashCode(); return 1; }}
再看結果:
[plain] view plain copy print ? a.equals(b) == true c.equals(d) == false set.size() == 3 com.darren.test.overide.CustomString@1 com.darren.test.overide.CustomString@1 A
a.equals(b) == truec.equals(d) == falseset.size() == 3com.darren.test.overide.CustomString@1com.darren.test.overide.CustomString@1A
只重寫hashCode方法,不重寫equals方法也不行
最後再改一改
[java] view plain copy print ? package com.darren.test.overide; public class CustomString { private String value; public CustomString() { this(“”); } public CustomString(String value) { this.value = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CustomString) { CustomString customString = (CustomString) obj; return customString.value.equals(value); } else { return false; } } @Override public int hashCode() { // return super.hashCode(); return 1; } }
package com.darren.test.overide;public class CustomString { private String value; public CustomString() { this(""); } public CustomString(String value) { this.value = value; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CustomString) { CustomString customString = (CustomString) obj; return customString.value.equals(value); } else { return false; } } @Override public int hashCode() { // return super.hashCode(); return 1; }}
最後結果:
[plain] view plain copy print ? a.equals(b) == true c.equals(d) == true set.size() == 2 com.darren.test.overide.CustomString@1 A
a.equals(b) == truec.equals(d) == trueset.size() == 2com.darren.test.overide.CustomString@1A
可以了,證明需要重寫equals方法和hashCode方法,來看原理:
java.lnag.Object中對hashCode的約定:
1. 在一個應用程式執行期間,如果一個對象的equals方法做比較所用到的資訊沒有被修改的話,則對該對象調用hashCode方法多次,它必須始終如一地返回同一個整數。
2. 如果兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。
3. 如果兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不同的整數結果。但如果能不同,則可能提高散列表的效能。
在HashSet中,基本的操作都是有HashMap底層實現的,因為HashSet底層是用HashMap儲存資料的。當向HashSet中添加元素的時候,首先計算元素的hashcode值,然後用這個(元素的hashcode)%(HashMap集合的大小)+1計算出這個元素的儲存位置,如果這個位置位空,就將元素添加進去;如果不為空白,則用equals方法比較元素是否相等,相等就不添加,否則找一個空位添加。
如下是HashSet的部分源碼:
[java] view plain copy print ? package java.util; public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; // 底層使用HashMap來儲存HashSet中所有元素。 private transient HashMap<E,Object> map; // 定義一個虛擬Object對象作為HashMap的value,將此對象定義為static final。 private static final Object PRESENT = new Object(); /** * 預設的無參構造器,構造一個空的HashSet。 * * 實際底層會初始化一個空的HashMap,並使用預設初始容量為16和載入因子0.75。 */ public HashSet() { map = new HashMap<E,Object>(); }