Java進階--Set介面
Set介面
java.util.Set
介面和java.util.List
介面一樣,同樣繼承自Collection
介面,它與Collection
介面中的方法基本一致,並沒有對Collection
介面進行功能上的擴充,只是比Collection
介面更加嚴格了。與List
介面不同的是,Set
介面中元素無序,並且都會以某種規則保證存入的元素不出現重複。
Set
集合有多個子類,這裡我們介紹其中的java.util.HashSet
、java.util.LinkedHashSet
這兩個集合。
tips:Set集合取出元素的方式可以採用:迭代器、增強for。
1.1 HashSet集合
1.1.1 HashSet集合介紹
java.util.HashSet
是Set
介面的一個實現類,它所儲存的元素是不可重複的,並且元素都是無序的(即存取順序不一致)。java.util.HashSet
底層的實現其實是一個java.util.HashMap
支援,由於我們暫時還未學習,先做了解。
HashSet
是根據物件的雜湊值來確定元素在集合中的儲存位置,因此具有良好的存取和查詢效能。保證元素唯一性的方式依賴於:hashCode
與equals
方法。
我們先來使用一下Set集合儲存,看下現象,再進行原理的講解:
package 集合和泛型.Set; import java.util.HashSet; import java.util.Iterator; import java.util.Set; /* HashSet的特點: 1. 不允許儲存重複的元素 2. 沒有索引,沒有帶索引的方法,也不能使用普通的for迴圈遍歷 3. 是一個無序的集合,儲存元素和取出元素的順序有可能一致 4. 底層是一個Hash表結構: 查詢速度非常快。 */ public class Demo01HashSet { public static void main(String[] args) { Set<Integer> set = new HashSet<>(); set.add(1); set.add(2); // set.add(2); set.add(3); set.add(4); // 使用迭代器變數 Iterator it = set.iterator(); while(it.hasNext()){ System.out.println(it.next()); } System.out.println("---------------"); for(Integer s: set){ System.out.println(s); } } }
1.1.2 HashSet集合儲存資料的結構(雜湊表)
什麼是雜湊表呢?
在JDK1.8之前,雜湊表底層採用陣列+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查詢的效率較低。
在JDK1.8中,雜湊表儲存採用陣列+連結串列+紅黑樹實現,當連結串列長度超過閾值(8)時,將連結串列轉換為紅黑樹,這樣大大減少了查詢時間。
特點: 查詢速度快。
簡單的來說,雜湊表是由陣列+連結串列+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下圖所示。
儲存流程圖:
package 集合和泛型.Set;
import java.util.HashSet;
/*
Set集合不允許儲存重複元素的原理
*/
public class Demo02HashSetSavaString {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
String s1 = new String("abc");
String s2 = new String("abc");
String s3 = new String("重地");
String s4 = new String("通話");
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add("abc");
System.out.println(set);
}
}
1.1.3 HashSet儲存自定義型別元素
給HashSet中存放自定義型別元素時,需要重寫物件中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的物件唯一
建立自定義Student類
package 集合和泛型.Set;
import java.util.HashSet;
import java.util.Objects;
/*
HashSet儲存自定義型別的元素
Set集合報錯元素唯一
儲存的元素(String,Integer,.... Student,Person ),必須重寫hashCode方法和equals方法
要求:
同名,同年齡的人視為一個人
*/
class Student{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return getAge() == student.getAge() &&
Objects.equals(getName(), student.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
}
public class Demo03HashSetSavePerson {
public static void main(String[] args) {
// 建立HashSet集合儲存Student
HashSet<Student> set = new HashSet<>();
Student stu1 = new Student("張三", 18);
Student stu2 = new Student("張三", 18);
Student stu3 = new Student("張三", 18);
set.add(stu1);
set.add(stu2);
set.add(stu3);
System.out.println(set); // 沒重寫equals和hashCode方法,重複的值也存入了
System.out.println(stu1.hashCode());
System.out.println(stu2.hashCode());
System.out.println(stu1==stu2);
System.out.println(stu1.equals(stu2));
// 沒重寫
//[Student{name='張三', age=18}, Student{name='張三', age=18}, Student{name='張三', age=18}]
//764977973
//381259350
//false
//false
//重寫後
//[Student{name='張三', age=18}]
//24022538
//24022538
//false
//true
}
}
1.2 LinkedHashSet
我們知道HashSet保證元素唯一,可是元素存放進去是沒有順序的,那麼我們要保證有序,怎麼辦呢?
在HashSet下面有一個子類java.util.LinkedHashSet
,它是連結串列和雜湊表組合的一個數據儲存結構。
演示程式碼如下:
package 集合和泛型.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
/*
java.util.LinkedHashSet集合, extends HashSet集合
LinkedHashSet集合的特點:
底層是一個雜湊表(陣列+連結串列+紅黑樹)+ 連結串列,多了一條連結串列(記錄元素儲存順序),保證資料的有序
*/
public class Demo04LinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("www");
set.add("abc");
set.add("abc");
set.add("java");
System.out.println(set); //[abc, java, www] 無序,不允許重複
LinkedHashSet<String> linkset = new LinkedHashSet<>();
linkset.add("www");
linkset.add("abc");
linkset.add("abc");
linkset.add("java");
System.out.println(linkset); //[www, abc, java] 有序,不允許重複,先存入先取出
}
}
1.3 可變引數
在JDK1.5之後,如果我們定義一個方法需要接受多個引數,並且多個引數型別一致,我們可以對其簡化成如下格式:
修飾符 返回值型別 方法名(引數型別... 形參名){ }
其實這個書寫完全等價與
修飾符 返回值型別 方法名(引數型別[] 形參名){ }
只是後面這種定義,在呼叫時必須傳遞陣列,而前者可以直接傳遞資料即可。
JDK1.5以後。出現了簡化操作。... 用在引數上,稱之為可變引數。
同樣是代表陣列,但是在呼叫這個帶有可變引數的方法時,不用建立陣列(這就是簡單之處),直接將陣列中的元素作為實際引數進行傳遞,其實編譯成的class檔案,將這些元素先封裝到一個數組中,在進行傳遞。這些動作都在編譯.class檔案時,自動完成了。
程式碼演示:
public class ChangeArgs {
public static void main(String[] args) {
int[] arr = { 1, 4, 62, 431, 2 };
int sum = getSum(arr);
System.out.println(sum);
// 6 7 2 12 2121
// 求 這幾個元素和 6 7 2 12 2121
int sum2 = getSum(6, 7, 2, 12, 2121);
System.out.println(sum2);
}
/*
* 完成陣列 所有元素的求和 原始寫法
public static int getSum(int[] arr){
int sum = 0;
for(int a : arr){
sum += a;
}
return sum;
}
*/
//可變引數寫法
public static int getSum(int... arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
}
tips: 上述add方法在同一個類中,只能存在一個。因為會發生呼叫的不確定性
注意:如果在方法書寫時,這個方法擁有多引數,引數中包含可變引數,可變引數一定要寫在引數列表的末尾位置。