Java 之路 (十一) -- 持有物件(Collection、List、Set、Queue、Map、Iterator、foreach)
本章將簡單介紹一下常用的集合類的特點,同時並不會深入原始碼分析原理,本文目的僅僅在於對 Java 集合類有一個整體認識
關於 API,本文不涉及過多,建議直接檢視 Java 官方文件
1. 容器概述
1.1 引入原因
Java 中,陣列用來儲存一組物件,但是陣列具有固定的尺寸。於是為了解決陣列長度固定,無法應對物件數量未知的情況,引入容器/集合類。
容器用途就是儲存物件。
1.2 容器層級
容器有兩大山脈:Collection 和 Map,其層次關係大致如下。
先來個基礎的:
再來個複雜一點的:
其中:
- Collection 介面是集合類的根介面,本身沒有實現類。
- Map 是容器的另一個根介面,與 Collection 相互獨立
- Iterator 是所有容器都實現的介面,用於遍歷容器中元素。
- 另外 Collections 和 Arrays 是 Object 的直接子類,其中包含了一系列關於 容器 和 陣列 的靜態方法
1.3 型別安全
通過 泛型 指定引數型別,即指定這個容器例項可以儲存的型別。通過使用泛型,可以在編譯期防止將錯誤型別的物件放置到容器中。
保證 型別安全 僅僅是 Java 泛型的其中一個作用。後續我們會學習更多關於 泛型的知識(劇透一下,很複雜)
舉個例子:
List<String> list = new ArrayList<String>();
//先不用管 List 和 ArrayList 是啥,就先當作是一個集合就好
//通過指定泛型為 String,那麼這個集合就是用來存放 String 型別的,放入其他型別的東西都會出錯。
2. Collection
Collection 是一個介面,它下面的 List,Set,Queue 同樣也都是介面。
一個獨立元素的序列,這些元素都服從一條或多條規則。其中 List 必須按照插入的順序儲存元素;Set 不能有重複元素;Queue 按照排隊規則來確定物件的產生順序(通常與插入順序相同)
2.1 List
List 是一個介面,它承諾可以將元素維護在特定的序列中。List 介面在 Collection 的基礎上新增大量的方法,使得可以在 List 中間插入和移除元素。
- 有序
- 元素可重複
下面主要介紹 兩種 List :ArrayList 和 LinkedList。
2.1.1 ArrayList
優點在於隨機訪問元素快,但是在中間插入和移除比較慢
原因是 ArrayList 底層是用陣列實現的,這也是為什麼讀取時和陣列效率相同,時間複雜度都是1.
2.1.2 LinkedList
優點是善於在中間插入和移除元素,提供了優化的順序訪問,相反缺點是隨機訪問較慢
原因是底層是使用鏈式儲存
同時,LinkedList 可以實現比 ArrayList 更多的功能特性:LinkedList 支援棧、佇列和雙端佇列。
2.1.3 Stack
Stack(棧)通常指 “後進先出”(LIFO)的容器,最後一個壓入棧的元素,第一個彈出棧。
可以想象為彈簧,最先放上彈簧的被放在最下面,後放上去的在上面,每次彈出來一個時,上面的(後放的)先被彈出。
Stack 是通過 LinkedList 實現的。
Stack 繼承自 Vector,而 Vector 已被棄用。
2.2 Set
Set 也是一個集合,但是它不儲存重複的元素。
Set 具有和 Collection 完全一樣的介面,因此沒有任何額外的功能,實際上 Set 就是 Collection,只不過行為不同。
繼承與多型思想的應用:表現不同的行為
下面簡單介紹兩個實現類:HashSet 和 TreeSet
2.2.1 HashSet
查詢速度塊。HashSet 使用了雜湊,雜湊的價值在於速度 – 雜湊使得查詢更加快速
儲存無序
HashSet 底層使用雜湊函式
2.2.2 TreeSet
儲存有序
TreeSet 將元素儲存在紅黑樹中,因此儲存結果有序。
2.2.3 LinkedHashSet
儲存有序
通過連結串列儲存新增的順序
2.3 Queue
Queue (佇列)是典型的 先進先出 的容器,即從一端放入事物,從另一端取出,且元素放入容器的順序和取出的順序是相同的。
可以想象一個傳送帶,先放上傳送帶的物品會首先到達。
同時 Queue 中允許重複元素。
LinkedList 實現了 Queue 介面,因此 LinkedList 可以用作 Queue 的一種實現(將LinkedList 可以向上轉型為 Queue)。
相關方法介紹:
方法 | 作用 | 說明 |
---|---|---|
add、offer | 將元素插入隊尾 | 佇列容量已滿時 add 會丟擲異常 offer會返回 false |
element、peek | 返回對頭元素(不刪除) | 佇列為空時 emelent 會丟擲異常 peek會返回 null |
remove、poll | 返回對頭元素,並將該元素從佇列移除 | 佇列為空時 remove 會丟擲異常 poll 會返回 null |
2.3.1 PriorityQueue
先入先出描述的是下一個元素應該是等待時間最長的元素
PriorityQueue(優先順序佇列)描述的是下一個元素應該是優先順序最高的元素。
當我們在 PriorityQueue 上呼叫 offer() 插入元素的時候,這個元素會在佇列中自動被排序,預設情況是自然排序。當然我們可以通過自定義 Comparator 來修改這個順序。
PriorityQueue 保證我們呼叫 peek、poll 等方法時,返回的是佇列中優先順序最高的元素。
3. Map
Map 就是一組成對的 “鍵值對” 物件,允許使用鍵來查詢值。Map 可以將物件對映到其他物件上,在實際開發中使用非常廣。
主要方法介紹:
方法 | 用途 |
---|---|
put | 新增鍵值對元素 |
get | 返回與鍵對應的值 |
containsKey | 查詢是否包含某個鍵 |
containsValue | 查詢是否包含某個值 |
keySet | 返回鍵的 set |
entrySet | 返回鍵值對的 set |
values | 返回值的 collection |
3.1 HashMap
查詢速度很快
儲存無序
原因在於,底層使用了 雜湊函式
鍵可以是 null,鍵值不可重複(重複會覆蓋舊的)
3.2 TreeMap
按照比較結果的升序儲存鍵
紅黑樹儲存
3.3 LinkedHashMap
按照插入順序儲存鍵
保留了 HashMap 的查詢速度
底層使用了雜湊函式 ,同時用一個連結串列儲存插入順序。
4. 迭代器
4.1 概念
Java 中的迭代器(Iterator):
- 是一個輕量級物件:建立代價小
- 工作是遍歷並選擇序列中的物件,不關注容器中元素的數量
- 只能單向移動
4.2 用處:
- 使用方法 iterator() 要求容器返回一個 Iterator,此時 Iterator 將準備好返回第一個元素
- 使用 next() 獲得序列下一個元素
- 使用 hasNext() 檢查序列中是否還有元素
- 使用 remove() 將迭代器最近返回的元素刪除
4.3 ListIterator
ListIterator 是 Iterator 的子型別,只能用於各種 List 類的訪問。
ListIterator 可以雙向移動,它可以產生相對於迭代器在列表中指向的當前位置的前一個(previous()方法)和後一個元素的索引(next() 方法)。
4.4 Iterator 與 foreach
foreach 除了可以作用於陣列,還可以應用於所有的 Collection 物件。這是因為 Collection 繼承了 Iterable 的介面。該介面包含一個能夠產生 iterator 的 iterator() 物件,並且 Iterable 介面被 foreach 用來在序列中移動。
public class IterableClass implements Iterable<String> {
protected String[] words=("Hello Java").split(" ");
public Iterator<String> iterator(){
return new Iterator<String>(){
private int index=0;
public boolean hasNext() {
return index<words.length;
}
public String next() {
return words[index++];
}
public void remove() {
}
};
}
public static void main(String[] args){
for(String s : new iterableClass())
System.out.println(s + "");
}
}
//輸出:
//Hello Java
5. Collections 和 Arrays
Collections 和 Arrays 都是 Object 的直接子類,裡面封裝了一系列關於集合和陣列的靜態方法。
部分方法如下:
Collections.addAll():用於將一些列元素加入到 Collection
public static <T> boolean addAll(Collection<? super T> c, T... elements)
Arrays.asList():將陣列(可變引數列表)寫入到一個列表,並將該列表返回
public static <T> List<T> asList(T... a)
底層表示為陣列,因此不能修改尺寸。如果呼叫add() 或 delete(),有可能改變陣列尺寸,因此會報錯。
Arrays.toString():產生陣列的可打印表示。
總結
Java 提供大量持有物件的方式:
- 陣列是將數字和物件聯絡起來,它儲存明確的物件,查詢物件時候不需要對查詢結果進行轉換,它可以是多維的,可以儲存基本型別的資料,但是陣列一旦生成,其容量不能改變。所以陣列是不可以直接刪除和新增元素。
- Collection 儲存單一的元素,而 Map 儲存相關聯的值鍵對,有了 Java 泛型,可以指定容器存放物件型別,不會將錯誤型別的物件放在容器中,取元素時候也不需要轉型。而且 Collection 和 Map 都可以自動調整其尺寸。容器不可以持有基本型別。
- 像陣列一樣,List 也建立數字索引和物件的關聯,因此,陣列和 List 都是排好序的容器,List 可以自動擴容
- 如果需要大量的隨機訪問就要使用 ArrayList,如果要經常從中間插入和刪除就要使用 LinkedList。
- 各種 Queue 和 Stack 由 LinkedList 支援
- Map 是一種將物件(而非數字)與物件相關聯的設計。HashMap 用於快速訪問,TreeMap 保持鍵始終處於排序狀態,所以不如 HashMap 快,而 LinkedHashMap 保持元素插入的順序,但是也通過雜湊提供了快速訪問的能力
- Set 不接受重複的元素,HashSet 提供最快的訪問能力,TreeSet 保持元素排序狀態,LinkedHashSet 以插入順序儲存元素。
- 不應使用過時的 Vector、HashTable
回顧一下開篇的簡圖:
可以發現其實只有四種容器 – List、Set、Queue、Map。常用的用紅色標出,綠色標識介面,其他標識普通的類。
使用時需要注意容器類之間方法的獨立性:有些相同,而有些相差很遠。
上述即為本章的全部,共勉。