泛型 集合(List,Set) Map
泛型
泛型的本質是引數化型別,即允許在編譯時對集合進行型別檢查,從而避免安全問題,提高程式碼的複用性
泛型的具體定義與作用
- 定義:泛型是一種在編譯階段進行型別檢查的機制,它允許在類,方法,介面後透過
<>
來宣告型別引數.這些引數在編譯時會被具體的型別替換.java在執行時,會透過型別擦除機制,將泛型型別擦除,變為原始型別(如,String,Integer
),具體的例子將在”泛型的使用”中演示 - 作用:
- 型別安全:透過泛型,在編譯階段可以檢查到更多的型別錯誤,就不用再執行時丟擲
ClassCastException
- 消除強制轉化:在使用了泛型之後,很多的型別轉換都可以自動執行,減少了程式碼中的顯性強制轉換
- 提高了程式碼的複用性
泛型的使用
- 泛型類的使用:在類名後新增引數宣告部分(用
<>
包括起來),如class Box<T>
public class Box<T>
{
private T value;//定義泛型值
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();//例項化String類的泛型物件
stringBox.setValue("這是一個String型別正規化");
System.out.println(stringBox.getValue());
Box<Integer> integerBox = new Box<>();//例項化Integer類的泛型物件
integerBox.setValue(123);
System.out.println(integerBox.getValue());
}
}
- 泛型類的使用:與泛型類類似,在介面定義後新增類似引數宣告.如
interface<K,V>
public interface Pair <K,V>{ //泛型介面
K getKey(); //Pair 介面定義了兩個抽象方法 型別分別為 K,V
V getValue(); //K,V都是待定義的型別
}
public class SimplePair<K,V> implements Pair<K,V>{
private K key;
private V value;
//SimplePair類實現了 Pair介面 必須從寫其中的抽象方法
public SimplePair(K key, V value) {
this.key = key; //SimplePair的建構函式
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
public static void main(String[] args) {
//例項化 一個SimplePair物件 傳入引數為 Integer,String
SimplePair<Integer, String> integerStringSimplePair = new SimplePair<Integer, String>(1,"one");
//則K就代表Integer V就代表String
System.out.println(integerStringSimplePair.getKey());
System.out.println(integerStringSimplePair.getValue());
}
}
- 泛型方法:在方法返回型別前宣告型別引數,如
public <T> void printArray(T[] inputArray)
。泛型方法可以定義在泛型類中,也可以定義在非泛型類中。
public class Method {
public static <T> void printArray(T[] inputArray){ //定義了一個返回值為<T>的泛型函式
for (T element : inputArray) {
System.out.println(element);
}
}
public static void main(String[] args) {
Integer [] integers={1,2,3,4,5};
String [] strings={"abcdefg"};
printArray(integers);//呼叫泛型函式
printArray(strings);
}
}
- 型別通配識別符號:使用
?
表示型別實參,表示不確定的型別(或者是待定的型別),通常用於泛型方法,泛型類,泛型介面;通常應用於當你不確定該資料型別時
public static void printElements(List<?> list)//假設你要寫一個列印集合元素的方法
//當你不確定該集合的型別 則可以使用萬用字元
{
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
List<String> stringList=new ArrayList<>();
List<Integer> integerList=new ArrayList<>();
printElements(stringList);//可以列印String
printElements(integerList);//也可以列印Integer
}
- 上限萬用字元:上限萬用字元用於知道一個型別的上限,它允許你指定一個型別及其子型別,其使用格式為
<?extends Type >
List<? extends Number> listOfNum=new ArrayList<Integer>();//使用Integer是合法的
//因為Number的子類包括 Integer Double 等等
listOfNum.add(null);//也是合法的null屬於一切型別
在這個例子中,List<? extends Number>
表示列表可以持有Number
型別或其子型別(如Integer
、Double
等)的物件,但你不能往這個列表中新增除了null
之外的任何元素,因為編譯器不知道列表的確切型別
泛型中常見的型別引數
- T:表示任意型別,是Type的縮寫,常用於泛型類,方法,介面中
- K,V:分別表示鍵(key)和值(value),常用於鍵值對中,如
Map<K,V>
- E:表示元素(Element),常用於集合中如
List<E>
- N:表示數字(Number),常用於數字型別
- S, U, V等:用於表示第二、第三、第四個泛型型別引數,這些字母在程式碼中的使用已成為一種約定俗成的規範
集合
java中,集合框架是一組介面和類的集合,他們提供了一種資料儲存和操作的方式.java的集合框架主要包括兩大介面
Collection
和Map
Collection介面
-
Collection
是所有單列集合的根介面,其子介面包括List
,Set
,Queue
java.util.Collection下的介面和繼承類關係簡易結構圖:
java.util.Map下的介面和繼承類關係簡易結構圖:
List介面
List集合也被稱為序列,其允許有重複的元素.List介面的實現類主要有
ArrayList, LinkedList
Vector
ArrayList
底層使用陣列實現,不是執行緒安全,查詢速度塊,但插入速度慢
public static void main(String[] args) {
//建立ArrayList物件
List<String> list=new ArrayList<>();
//使用add()方法向陣列中新增元素
list.add("張三");
list.add("李四");
list.add("王五");
//使用get(index)方法獲取陣列下標為index的元素
System.out.println(list.get(0));
//list的增強for迴圈
for (String s : list) {
System.out.println(s);
}
}
LinkArray
底層使用雙向連結串列實現,查詢速度慢,但其增刪速度快,使用方法與
ArrayList
基本一致
public static void main(String[] args) {
List<String> list=new LinkedList<>(); //建立LinkedList物件
list.add("張三");//一下的使用方法與ArrayList一致
list.add("李四");
list.add("王五");
System.out.println(list.get(0));
for (String s : list) {
System.out.println(s);
}
Vector
底層與
ArrayList
一致都是使用陣列實現的,執行緒安全性高,但效率較低
public static void main(String[] args) {
List<String> list=new Vector<>();//建立Vector物件
list.add("張三");
list.add("李四");
list.add("王五");
System.out.println(list.get(0));
for (String s : list) {
System.out.println(s);
}
}
Set介面
其特點為無序集合,不允許有重複元素,包括主要實現類
HashSet
,LinkedSet
和TreeSet
HashSet
作為較為常用的Set集合,其底層是基於雜湊表實現的,這就決定了其無法新增重複的元素和無序性
HashSet
之所以能保證元素的唯一性是重寫了其hashCode()
方法和equals()
方法,具體操作為:
- HashSet在每次儲存元素的過程都會首先檢視其
hashCode()
值,看其雜湊值是否與以存入HashSet
的元素的雜湊值一致,若不一致則直接存入集合,若一致則進行步驟2 - 如果其雜湊值相同則繼續呼叫元素的
equals()
方法與雜湊值相同的元素進行依次比較,若返回值為ture,則說明重複則不新增,反之則新增
- 無序性:
HashSet
是基於雜湊表實現的,因此在新增元素時,不會按照其新增的順序排放,而是根據雜湊表原理,透過hash值存放. - 遍歷無需性:當使用迭代器或者增強for迴圈時,HashSet的遍歷也不是按照其元素插入的順序執行的,也不是按照任何可預測的順序執行的,而是基於雜湊表的內部結構決定的,則意味著對於相同的
HashSet
,在不同的JVM和實現方法下其遍歷順序都是不同的
HashSet<Integer> integerHashSet = new HashSet<>(); //建立HashSet物件
integerHashSet.add(1);
integerHashSet.add(1);//使用add方法向其插入元素
integerHashSet.add(2);
integerHashSet.add(-1);
for (Integer integer : integerHashSet) {
System.out.println(integer);
} //列印結果為 -1 1 2
LinkedHashSet
作為
HashSet
的子類,繼承了HashSet
的所有特性,即不允許集合中有重複元素,但與HashSet
不同的是LinkedHashSet
內部維護了一個雙向連結串列,用於實現按元素的插入的順序實現遍歷
- 底層資料邏輯:
LinkedHashSet
底層的資料結構包括一個陣列和一個雙向連結串列(或者是紅黑樹),這個陣列和雙向連結串列(或者紅黑樹)共同構成了LinkedHashMap
(本文將在下文講解到),的實現基礎,而LinkedHashSet
就是透過封裝LinkedHashMap
來實現其功能,即底層是基於LinkedHashMap
實現的 - 具體實現:
LinkedHashSet
,在新增元素時,都會呼叫LinkedHashMap
的put
方法來實現.LinkedHashMap
的put方法首先會計算插入元素的雜湊值,並根據雜湊值確定元素在陣列中的位置,然後,會在雙向連結串列(或紅黑樹)新增一個節點,儲存元素值,因此每次遍歷*LinkedHashSet
時實際上是遍歷其雙向連結串列(紅黑樹)*,從而保證了遍歷順序與元素插入順序一致
LinkedHashSet<Integer> integerLinkedHashSet = new LinkedHashSet<>();
//建立一個LinkedHashSet物件
integerLinkedHashSet.add(1);
integerLinkedHashSet.add(1);//新增元素
integerLinkedHashSet.add(2);
integerLinkedHashSet.add(-1);
for (Integer integer : integerLinkedHashSet) {
System.out.println(integer);
}//列印結果與插入順序一致 1 2 -1
TreeSet
TreeSet
是Set的子類,因此也保留的Set
介面的特性,特別的是TreeSet
是基於紅黑樹實現的
- 底層資料邏輯:
TreeSet
的底層實際上是基於TreeMap
作為底層儲存實現的,TreeSet內部維護了一個NavigableMap
(實際上就是TreeMap
的一個例項化物件),用於儲存元素,在這個對映中,鍵(key)就是TreeSet中的元素,而值(value)是一個固定的關係共享的Object物件,(在TreeSet中,這個Object物件被命名為PRESENT),用於表現值的存在性,不儲存特點的值.
以下是TreeSet內部程式碼結構:
- TreeSet的排序機制:
TreeSet
元素預設是根據自然順序或根據指定的Comparator
進行排序,如果沒有提供Comparator
則,TreeSet會按照元素自然排序;如果提供了Comparator
則使用Comparator
來確定元素的順序
public class NumComparator implements Comparator<Integer> {
//NumComparator類實現了Comparator介面
@Override//重寫了compare方法
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
}
TreeSet<Integer> integerTreeSet = new TreeSet<>(new NumComparator());
//傳入NumComparator物件表明該TreeSet以該方式排序元素
integerTreeSet.add(1);//新增元素
integerTreeSet.add(-1);
integerTreeSet.add(2);
for (Integer integer : integerTreeSet) {
System.out.println(integer);
}列印結果為[-1,1,2]
List與Set的常用方法
返回值型別 | 方法和描述 |
---|---|
boolean | add(E e) 將指定的元素追加到此列表的末尾。 |
void | add(int index, E element) 在此列表中的指定位置插入指定的元素。 |
boolean | addAll(Collection<? extends E> c) 按指定集合的Iterator返回的順序將指定集合中的所有元素追加到此列表的末尾。 |
boolean | addAll(int index, Collection<? extends E> c) 將指定集合中的所有元素插入到此列表中,從指定的位置開始。 |
void | clear() 從列表中刪除所有元素。 |
boolean | contains(Object o) 如果此列表包含指定的元素,則返回 true 。 |
E | get(int index) 返回此列表中指定位置的元素。 |
int | indexOf(Object o) 返回此列表中指定元素的第一次出現的索引,如果此列表不包含元素,則返回-1。 |
boolean | isEmpty() 如果此列表不包含元素,則返回 true 。 |
Iterator |
iterator() 以正確的順序返回該列表中的元素的迭代器。 |
int | lastIndexOf(Object o) 返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。 |
E | remove(int index) 刪除該列表中指定位置的元素。 |
boolean | remove(Object o) 從列表中刪除指定元素的第一個出現(如果存在)。 |
boolean | removeAll(Collection<?> c) 從此列表中刪除指定集合中包含的所有元素。 |
E | set(int index, E element) 用指定的元素替換此列表中指定位置的元素。 |
int | size() 返回此列表中的元素數。 |
Object[] | toArray() 以正確的順序(從第一個到最後一個元素)返回一個包含此列表中所有元素的陣列。 |
Map(字典)
Map是一種將鍵(key)對映到值(value)的物件,它提供了一種鍵值對的儲存機制,其中每個鍵都唯一對映到一個值,這種結構有利於快速查詢,插入和刪除值
Map的儲存結構:
HashMap
HashMap
是基於雜湊表實現的,它允許使用null
鍵和null
值,HashMap不保證對映的順序,即遍歷Map時元素的順序可能與插入順序不同,HashMap底層主要維護一個陣列和一個連結串列
HashMap
的底層原理:
HashMap
底層維護了一個陣列,被稱為”桶”,用來儲存多個鍵值對,沒有指定初始量時,陣列預設長度是16- 當插入資料時兩個不同的鍵產生了雜湊衝突,這時就會透過HashMap底層維護的連結串列來解決雜湊衝突
HashMap<Integer, String> integerStringHashMap = new HashMap<>();//建立HashMap物件
integerStringHashMap.put(1,"one");//Map使用put新增元素
integerStringHashMap.put(-1,"-one");
integerStringHashMap.put(2,"two");
for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}//對於Map有特殊的遍歷方式,本文將會在下文解析
//輸出[-1 -one,1 one,2 two]
TreeMap
TreeMap
是基於紅黑樹實現的Map介面,基於這種資料結構讓TreeMap
可以在log(n)時間複雜度完成containsKey、get、put和remove
等操作.TreeMap是實現TreeSet的基礎
- 有序性:由於基於紅黑樹實現儲存,則保證了TreeMap是有序的,這種有序可以是自然順序(即插入順序),或者可以根據指定
Comparator
實現
TreeMap<Integer, String> integerStringHashMap = new TreeMap<>();//建立TreeMap物件
integerStringHashMap.put(1,"one");//Map使用put新增元素
integerStringHashMap.put(-1,"-one");
integerStringHashMap.put(2,"two");
for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}//對於Map有特殊的遍歷方式,本文將會在下文解析
//輸出[-1 -one,1 one,2 two]
HashTable
HashTable
底層原理與HashMap
十分相似,但與HashMap
相比HashTable的put,get,remove
加上了同步塊,和使用了this
鎖,則使得HashTable執行緒是安全的,但效能較低
- 鍵和值的約束:HashTable是不允許使用null作為鍵和值的,否則會報出
NullPointerException
異常
HashMap<Integer, String> integerStringHashMap = new HashMap<>();//建立HashMap物件
integerStringHashMap.put(1,"one");//Map使用put新增元素
integerStringHashMap.put(-1,"-one");
integerStringHashMap.put(2,"two");
for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}//對於Map有特殊的遍歷方式,本文將會在下文解析
//輸出[-1 -one,1 one,2 two]
LinkedHashMap
LinkedHashMap
繼承了HashMap
,Linked的內部維護了一個雙向連結串列用於保證元素的順序
LinkedHashMap
內部結構:其內部結合了雜湊表和雙向連結串列兩種資料結構,雜湊表用於快速檢索元素,雙向連結串列用於維護元素的順序- 插入和訪問:當元素被插入
LinkedHashMap
時,會在連結串列的尾部新增一個新的節點。如果設定了按訪問順序排列(透過建構函式或setAccessOrder
方法),則每次訪問元素時,會將該節點移動到連結串列的尾部,以保持訪問順序
LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();//建立LinkedHashMap物件
integerStringHashMap.put(1,"one");//Map使用put新增元素
integerStringHashMap.put(-1,"-one");
integerStringHashMap.put(2,"two");
for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) {
System.out.println(entry.getKey()+" "+entry.getValue());
}//對於Map有特殊的遍歷方式,本文將會在下文解析
//輸出[1 one,-1 -one,2 two]
Map的遍歷方式
由於Map資料結構的特性,(使用鍵值對),因此必須指定要遍歷的條件,例如按鍵或按值遍歷等等
-
使用
entrySet()
和增強for迴圈:透過
entrySet()
方法,Map可以被轉換為一個包含Map.Entry物件的Set集合,其中每個Map.Entry物件都代表Map中的一個鍵值對。然後,可以使用增強for迴圈來遍歷這個Set集合LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();//建立LinkedHashMap物件 for (Map.Entry<Integer, String> entry : integerStringHashMap.entrySet()) { System.out.println(entry.getKey()+" "+entry.getValue()); }
-
使用
KeySet()
和增強for迴圈:如果只對Map的鍵感興趣,可以使用
keySet()
方法獲取一個包含Map中所有鍵的Set集合,然後遍歷這個集合。如果需要獲取對應的值,可以透過鍵來從Map中獲取。LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>(); for (Integer integer : integerStringHashMap.keySet()) {//其中integer表示Map的鍵值 //透過Map方法的get(key)方法返回的是透過key對映的value System.out.println(integer+integerStringHashMap.get(integer)); }
-
使用
values()
和增強for迴圈:與
KeySet()
方法同理,如果只對Map的值感興趣,可以使用values()
方法獲取一個包含Map中所有值的Collection集合,然後遍歷這個集合。但請注意,這種方式無法直接獲取到對應的鍵。只能獲取其value值LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>();//建立LinkedHashMap物件 for (String value : integerStringHashMap.values()) { System.out.println(value); }
-
使用
entrySet()
和Iterator
迭代器使用
entrySet()
方法結合Iterator
迭代器來遍歷Map
中的鍵值對是一種常見的做法,尤其當需要同時訪問鍵和值時,整體是透過while迴圈實現的-
在使用前必須使用
interator()
方法構建一個interator
物件,並且需要透過Iterator
的hasNext()
方法檢查是否還有下一個元素。 -
使用
Iterator
的next()
方法獲取下一個Map.Entry
物件,從Map.Entry
物件中使用getKey()
和getValue()
方法分別獲取鍵和值。LinkedHashMap<Integer, String> integerStringHashMap = new LinkedHashMap<>(); Iterator<Map.Entry<Integer, String>> iterator = integerStringHashMap.entrySet().iterator(); //使用interator()建立一個intertor物件這步其實為聯合方法可以分為一下兩步 while (iterator.hasNext()) { Map.Entry<Integer, String> entry = iterator.next(); //每次透過next()方法獲取entries的下一個實體 儲存再entry中 Integer key=entry.getKey();//使用迭代器的getKey()方法可以獲取鍵 String value=entry.getValue();//getValue()方法可以獲取值 System.out.println(key+value); }
Iterator<Map.Entry<Integer, String>> iterator =integerStringHashMap.entrySet().iterator();
//使用interator()建立一個intertor物件這步其實為聯合方法可以分為一下兩步- 先使用
entrySet()
方法建立一個Set集合:
Set<Map.Entry<Integer, String>> entries = integerStringHashMap.entrySet();
其中
Map.Entry<>
表示Map中的一個實體- 再使用
interator()
構造一個interator
物件
Iterator<Map.Entry<Integer, String>> iterator = entries. Iterator();
- 先使用
-