Hashset 實現 set 接口,底層基於 Hashmap 實現, 但與 Hashmap 不一樣的實 Hashmap 存儲鍵值對,Hashset 僅存儲對象。java
HashSet 使用成員對象來計算 hashcode 值。面試
在《Head fist java》一書中有描述:數組
當你把對象加入 HashSet 時,HashSet 會先計算對象的 hashcode 值來判斷對象加入的位置,同時也會與其餘加入的對象的 hashcode 值做比較,若是沒有相符的 hashcode,HashSet 會假設對象沒有重複出現。可是若是發現有相同 hashcode 值的對象,這時會調用 equals()方法來檢查 hashcode 相等的對象是否真的相同。若是二者相同,則覆蓋舊元素。源碼分析
這裏看到不少文章說: 若是 equals()方法相等,HashSet 就不會讓加入操做成功。根據 hashmap 的 put()方法源碼可知,其實是覆蓋操做,雖然覆蓋對象的 key 和 value 都徹底一致。測試
hashCode()與 equals()的相關規定:this
==與 equals 的區別code
首先查看下源碼結構,發現該類源碼相對比較簡單對象
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ // 內部存儲在hashmap中 public HashSet() { map = new HashMap<>(); }
private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; }
能夠看到添加的對象直接做爲 Hashmap 的 key, 而 value 是 final 修飾的空對象。繼承
根據以前對 Java 面試必問之 Hashmap 底層實現原理(JDK1.8) 中 put()
方法的解讀能夠知道:接口
在 Hashmap 中首先根據 hashCode 尋找數組 bucket,當 hash 衝突時,須要比較 key 是否相等,相等則覆蓋,不然經過拉鍊法進行處理。在 Hashset 中存儲的對象做爲 key,因此存儲對象須要重寫 hashCode()
和 equals()
方法。
再來看一組示例
public class Demo2 { public static void main(String[] args) { HashSet<Object> hashSet = new HashSet<>(); hashSet.add("a"); hashSet.add("b"); hashSet.add("c"); hashSet.add("a"); System.out.println(hashSet); } }
結果
[a, b, c]
分析
查看字符串源碼.字符串重寫了 hashCode()和 equals 方法, 因此結果符合預期
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } }
首先咱們建立一個 user
對象
@Getter@Setter @AllArgsConstructor @ToString public class User { private String username; }
根據 set 集合的屬性,set 中的元素是不重複的,如今測試下
public class Demo { public static void main(String[] args) { HashSet<Object> hashSet = new HashSet<>(); hashSet.add(new User("a")); hashSet.add(new User("b")); hashSet.add(new User("c")); hashSet.add(new User("a")); System.out.println(hashSet); } }
結果輸出
[User(username=a), User(username=c), User(username=b), User(username=a)]
怎麼會有重複的呢? 和預期結果不符呀。其實根據上邊的源碼咱們已經知道緣由了,打印 hash 值確認下
[901506536, 1513712028, 747464370, 1018547642]
java 中對象默認繼承頂級父類 Object。在 Object 類中源碼以下:
public native int hashCode(); // 比較內存地址 public boolean equals(Object obj) { return (this == obj); }
重寫 equals()和 hashCode()方法。(這裏偷了個懶,感興趣的你們能夠本身重寫下這 2 個方法)
@Getter@Setter @AllArgsConstructor @ToString @EqualsAndHashCode public class User extends Object{ private String username; }
再次輸出發現結果惟一了
[User(username=a), User(username=b), User(username=c)]
其實 HashSet 的一些東西都是用 HashMap 來實現的,若是 HashMap 的源碼已經閱讀過的話基本上沒有什麼問題。這多是我寫的最輕鬆的一篇文章。