集合中的HashSet底層是通過Hash表實現,HashSet的特點是元素唯一,但用到Hash表就跟hashCode()有了密不可分的聯系,所以HashSet的唯一性是通過hashCode()方法來保證,當然光有HashCode()還不夠,還有equals()也用到。從底層(HashMap的put()方法)實現代碼來看,就可以清楚地看到這一點。
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
以上代碼是HashMap的put()方法源碼。那么HashSet到底是如何保證元素的唯一性,還是通過例子來說明這一點。同樣,還是以HashSet存儲自定義對象為例,先創建一個Person類,成員:name、age、無參、有參構造及getters和setters。
package cn.dolphin; public class Person{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person() { super(); // TODO Auto-generated constructor stub } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
這里先沒有重寫繼承自Object類的hashCode()和equals()方法。
package cn.dolphin; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class HashSetDemo { public static void main(String[] args) { //創建集合對象 Set<Person> set = new HashSet<>(); //創建對象元素 Person p1 = new Person("諸葛亮",39); Person p2 = new Person("趙子龍",36); Person p3 = new Person("關雲長",38); Person p4 = new Person("關雲長",38); Person p5 = new Person("關雲長",38); //將對象元素添加到集合 set.add(p1); set.add(p2); set.add(p3); set.add(p4); set.add(p5); //使用iterator()對集合遍歷 for (Iterator<Person> it = set.iterator(); it.hasNext();) { Person p = it.next(); System.out.println(p); } } }
運行程序的結果看到[關雲長,38]出現了三次,這說明沒有保證元素的唯一。現在我們在Person類中重寫hashCode()和equals()。使用eclipse直接右鍵"Source->Generate hashCode() and equals()..."自動生成代碼。
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Person)) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; }
再次運行程序,看到結果中[關雲長,38]只出現了一次,說明已經對相同元素進行了過濾。再回過頭來看看上面的源代碼,里面的if語句"if (e.hash == hash && ((k = e.key) == key || key.equals(k)))"使用的"&&",這個邏輯運算符的使用,還是有必要在這里啰嗦一下,只有"&&"的左邊為true才會對右邊進行判斷,左邊如果false,就不再看右邊,這不禁使我想起一道面試題,扯遠了,這個最后說。那么"&&"用在這里就意味着,如果左邊的hashCode()判斷false,會直接添加元素,不用再判斷equals(),如果左邊true,才會繼續判斷equals()。因為"&&"具有短路功能,這就是HashSet保證元素唯一的原理。
下面說說剛才提到的面試題。
int x = 1, y = 1; if(x++ > 3 && ++y > 3){ ++x; y++; } System.out.println(x); System.out.println(y);
