Set接口簡介
Set接口和List接口一樣,同樣繼承自Collection接口,它與Collection接口中的方法基本一致,并沒有對Collection接口進行功能上的擴充,只是比Collection接口更加嚴格了。與List接口不同的是,Set接口中元素無序,并且都會以某種規則保證存入的元素不出現重復。
Set接口主要有兩個實現類,分別是HashSet和TreeSet。其中,HashSet是根據對象的哈希值來確定元素在集合中的存儲位置,具有良好的存取和查找性能。TreeSet則是以二叉樹的方式來存儲元素,它可以實現對集合中的元素進行排序。
HashSet集合
HashSet是Set接口的一個實現類,它所存儲的元素是不可重復的,并且元素都是無序的。接下來通過一個案例演示HashSet集合的用法。
Java import java.util.HashSet; import java.util.Iterator;
public class Example07 { public static void main(String[] args) { HashSet set = new HashSet(); // 創建HashSet集合 set.add("張三"); // 向該Set集合中添加字符串 set.add("李四"); set.add("王五"); set.add("李四"); // 向該Set集合中添加重復元素 Iterator it = set.iterator(); // 獲取Iterator對象 while (it.hasNext()) { // 通過while循環,判斷集合中是否有元素 Object obj = it.next(); // 如果有元素,就通過迭代器的next()方法獲取元素 System.out.println(obj); } } }
|
從打印結果可以看出,取出元素的順序與添加元素的順序并不一致,并且重復存入的字符串對象“李四”被去除了,只添加了一次。
HashSet集合之所以能確保不出現重復的元素,是因為它在存入元素時做了很多工作。當調用HashSet集合的add()方法存入元素時,首先調用當前存入對象的hashCode()方法獲得對象的哈希值,然后根據對象的哈希值計算出一個存儲位置。如果該位置上沒有元素,則直接將元素存入,如果該位置上有元素存在,則會調用equals()方法讓當前存入的元素依次和該位置上的元素進行比較,如果返回的結果為false就將該元素存入集合,返回的結果為true則說明有重復元素,就將該元素舍棄。HashSet存儲元素的流程如下圖所示。
根據前面的分析不難看出,當向集合中存入元素時,為了保證HashSet正常工作,要求在存入對象時,重寫Object類中的hashCode()和equals()方法。在上述案例中將字符串存入HashSet時,String類已經重寫了Object類中的hashCode()和equals()方法。但是如果將自定義的Student對象存入HashSet,結果又如何呢?
接下來通過一個案例演示向HashSet存儲字符串。
Java import java.util.HashSet;
class Student { String id; String name;
// 創建構造方法 public Student(String id, String name) { this.id = id; this.name = name; }
// 重寫toString()方法 @Override public String toString() { return id + ":" + name; } } public class Example08 { public static void main(String[] args) { HashSet hs = new HashSet(); // 創建HashSet集合 Student stu1 = new Student("1", "張三"); // 創建Student對象 Student stu2 = new Student("2", "李四"); Student stu3 = new Student("2", "李四"); hs.add(stu1); hs.add(stu2); hs.add(stu3); System.out.println(hs); } }
|
在上述代碼的運行結果中,出現了兩個相同的學生信息“2:李四”,這樣的學生信息應該被視為重復元素,不允許同時出現在HashSet集合中。之所以沒有去掉這樣的重復元素,是因為在定義Student類時沒有重寫hashCode()和equals()方法。
接下來對Student類進行改寫,假設id相同的學生就是同一個學生,改寫后代碼見下。
Java import java.util.HashSet; import java.util.Objects;
class Student { String id; String name;
// 創建構造方法 public Student(String id, String name) { this.id = id; this.name = name; }
// 重寫toString()方法 @Override public String toString() { return id + ":" + name; }
// 重寫hashCode方法 @Override public int hashCode() { return id.hashCode(); // 返回id屬性的哈希值 }
// 重寫equals方法 @Override public boolean equals(Object o) { // 判斷是否是同一個對象,如果是,直接返回true if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; // 判斷對象是否為Student類型 if (!(o instanceof Student)) return false; // 將對象強轉為Student類型 Student student = (Student) o; // return (id == student.id) || (id != null && id.equals(student.id)); return Objects.equals(id, student.id); } }
public class Example09 { public static void main(String[] args) { HashSet hs = new HashSet(); // 創建HashSet集合 Student stu1 = new Student("1", "張三"); // 創建Student對象 Student stu2 = new Student("2", "李四"); Student stu3 = new Student("2", "李四"); hs.add(stu1); hs.add(stu2); hs.add(stu3); System.out.println(hs); } }
|
在上述代碼中,Student類重寫了Object類的hashCode()和equals()方法。在hashCode()方法中返回id屬性的哈希值,在equals()方法中比較對象的id屬性是否相等,并返回結果。當調用HashSet集合的add()方法添加stu3對象時,發現它的哈希值與stu2對象相同,而且id.equals(student.id)返回true,HashSet集合認為兩個對象相同,因此重復的Student對象被成功去除了。
HashSet集合存儲的元素是無序的,如果想讓元素的存取順序一致,可以使用Java中提供的LinkedHashSet類,LinkedHashSet類是HashSet的子類,與LinkedList一樣,它也使用雙向鏈表來維護內部元素的關系。
接下來通過一個案例學習LinkedHashSet類的用法。
Java import java.util.Iterator; import java.util.LinkedHashSet;
public class Example10 { public static void main(String[] args) { LinkedHashSet set = new LinkedHashSet(); set.add("張三"); // 向該Set集合中添加字符串 set.add("李四"); set.add("王五"); Iterator it = set.iterator(); // 獲取Iterator對象 while (it.hasNext()) { // 通過while循環,判斷集合中是否有元素 Object obj = it.next(); System.out.println(obj); } } }
|
通過運行結果可以看出,元素迭代出來的順序和存入的順序是一致的。