1. 程式人生 > >Java 之路 (十一) -- 持有物件(Collection、List、Set、Queue、Map、Iterator、foreach)

Java 之路 (十一) -- 持有物件(Collection、List、Set、Queue、Map、Iterator、foreach)

本章將簡單介紹一下常用的集合類的特點,同時並不會深入原始碼分析原理,本文目的僅僅在於對 Java 集合類有一個整體認識

關於 API,本文不涉及過多,建議直接檢視 Java 官方文件

1. 容器概述

1.1 引入原因

Java 中,陣列用來儲存一組物件,但是陣列具有固定的尺寸。於是為了解決陣列長度固定,無法應對物件數量未知的情況,引入容器/集合類。

容器用途就是儲存物件。

1.2 容器層級

容器有兩大山脈:Collection 和 Map,其層次關係大致如下。

先來個基礎的:

img

再來個複雜一點的:

img

其中:

  • 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 提供大量持有物件的方式:

  1. 陣列是將數字和物件聯絡起來,它儲存明確的物件,查詢物件時候不需要對查詢結果進行轉換,它可以是多維的,可以儲存基本型別的資料,但是陣列一旦生成,其容量不能改變。所以陣列是不可以直接刪除和新增元素。
  2. Collection 儲存單一的元素,而 Map 儲存相關聯的值鍵對,有了 Java 泛型,可以指定容器存放物件型別,不會將錯誤型別的物件放在容器中,取元素時候也不需要轉型。而且 Collection 和 Map 都可以自動調整其尺寸。容器不可以持有基本型別。
  3. 像陣列一樣,List 也建立數字索引和物件的關聯,因此,陣列和 List 都是排好序的容器,List 可以自動擴容
  4. 如果需要大量的隨機訪問就要使用 ArrayList,如果要經常從中間插入和刪除就要使用 LinkedList。
  5. 各種 Queue 和 Stack 由 LinkedList 支援
  6. Map 是一種將物件(而非數字)與物件相關聯的設計。HashMap 用於快速訪問,TreeMap 保持鍵始終處於排序狀態,所以不如 HashMap 快,而 LinkedHashMap 保持元素插入的順序,但是也通過雜湊提供了快速訪問的能力
  7. Set 不接受重複的元素,HashSet 提供最快的訪問能力,TreeSet 保持元素排序狀態,LinkedHashSet 以插入順序儲存元素。
  8. 不應使用過時的 Vector、HashTable
    回顧一下開篇的簡圖:

不包含抽象類和遺留構件

可以發現其實只有四種容器 – List、Set、Queue、Map。常用的用紅色標出,綠色標識介面,其他標識普通的類。

使用時需要注意容器類之間方法的獨立性:有些相同,而有些相差很遠。

上述即為本章的全部,共勉。