day11 11 集合總結

11、集合總結

在編程時,可以使用數組來保存多個對象,但數組長度不可變化,一旦在初始化數組時指定了數組長度,這個數組長度就是不可變的。如果需要保存數量變化的數據,數組就有點無能爲力了。而且數組無法保存具有映射關係的數據,如成績表爲語文——79,數學——80,這種數據看上去像兩個數組,但這兩個數組的元素之間有一定的關聯關係。

爲了保存數量不確定的數據,以及保存具有映射關係的數據(也被稱爲關聯數組),Java 提供了集合類。集合類主要負責保存、盛裝其他數據,因此集合類也被稱爲容器類。Java 所有的集合類都位於 java.util 包下,提供了一個表示和操作對象集合的統一構架,包含大量集合接口,以及這些接口的實現類和操作它們的算法。

集合類和數組不一樣,數組元素既可以是基本類型的值,也可以是對象(實際上保存的是對象的引用變量),而集合裏只能保存對象(實際上只是保存對象的引用變量,但通常習慣上認爲集合裏保存的是對象)。

11.1 集合的分類

Java 集合類型分爲 Collection 和 Map,下面用兩張圖來表示對應的分類

黃色塊爲集合的接口,藍色塊爲集合的實現類。

11.1.1 Collection

在這裏插入圖片描述

接口名稱 作 用
Iterator 接口 集合的輸出接口,主要用於遍歷輸出(即迭代訪問)Collection 集合中的元素,Iterator 對象被稱之爲迭代器。迭代器接口是集合接口的父接口,實現類實現 Collection 時就必須實現 Iterator 接口。
Collection 接口 是 List、Set 和 Queue 的父接口,是存放一組單值的最大接口。所謂的單值是指集合中的每個元素都是一個對象。一般很少直接使用此接口直接操作。
Queue 接口 Queue 是 Java 提供的隊列實現,有點類似於 List。
Dueue 接口 是 Queue 的一個子接口,爲雙向隊列。
List 接口 是最常用的接口。是有序集合,允許有相同的元素。使用 List 能夠精確地控制每個元素插入的位置,用戶能夠使用索引(元素在 List 中的位置,類似於數組下標)來訪問 List 中的元素,與數組類似。
Set 接口 不能包含重複的元素。
Map 接口 是存放一對值的最大接口,即接口中的每個元素都是一對,以 key➡value 的形式保存。

11.1.2 Map

在這裏插入圖片描述

對於 Set、List、Queue 和 Map 這 4 種集合,Java 最常用的實現類分別是 HashSet、TreeSet、ArrayList、ArrayDueue、LinkedList 和 HashMap、TreeMap 等。我們先介紹這些實現類

類名稱 作用
HashSet 爲優化査詢速度而設計的 Set。它是基於 HashMap 實現的,HashSet 底層使用 HashMap 來保存所有元素,實現比較簡單
TreeSet 實現了 Set 接口,是一個有序的 Set,這樣就能從 Set 裏面提取一個有序序列
ArrayList 一個用數組實現的 List,能進行快速的隨機訪問,效率高而且實現了可變大小的數組
ArrayDueue 是一個基於數組實現的雙端隊列,按“先進先出”的方式操作集合元素
LinkedList 對順序訪問進行了優化,但隨機訪問的速度相對較慢。此外它還有 addFirst()、addLast()、getFirst()、getLast()、removeFirst() 和 removeLast() 等方法,能把它當成棧(Stack)或隊列(Queue)
TreeMap 可以對鍵對象進行排序

11.2 Collection接口介紹

簡介
Collection 接口是 List、Set 和 Queue 接口的父接口,通常情況下不被直接使用。Collection 接口定義了一些通用的方法,通過這些方法可以實現對集合的基本操作。定義的方法既可用於操作 Set 集合,也可用於操作 List 和 Queue 集合。Collection 是接口,不能對其實例化
通用集合方法

方法名稱 說明
boolean add(E e) 向集合中添加一個元素,如果集合對象被添加操作改變了,則返回 true。E 是元素的數據類型
boolean addAll(Collection c) 向集合中添加集合 c 中的所有元素,如果集合對象被添加操作改變了,則返回 true。
void clear() 清除集合中的所有元素,將集合長度變爲 0。
boolean contains(Object o) 判斷集合中是否存在指定元素
boolean containsAll(Collection c) 判斷集合中是否包含集合 c 中的所有元素
boolean isEmpty() 判斷集合是否爲空
Iteratoriterator() 返回一個 Iterator 對象,用於遍歷集合中的元素
boolean remove(Object o) 從集合中刪除一個指定元素,當集合中包含了一個或多個元素 o 時,該方法只刪除第一個符合條件的元素,該方法將返回 true。
boolean removeAll(Collection c) 從集合中刪除所有在集合 c 中出現的元素(相當於把調用該方法的集合減去集合 c)。如果該操作改變了調用該方法的集合,則該方法返回 true。
boolean retainAll(Collection c) 從集合中刪除集合 c 裏不包含的元素(相當於把調用該方法的集合變成該集合和集合 c 的交集),如果該操作改變了調用該方法的集合,則該方法返回 true。
int size() 返回集合中元素的個數
Object[] toArray() 把集合轉換爲一個數組,所有的集合元素變成對應的數組元素。

11.3 List集合(LinkedList,ArrayList,Vector)

11.3.1 介紹

List 是一個有序、可重複的集合,集合中每個元素都有其對應的順序索引。List 集合允許使用重複元素,可以通過索引來訪問指定位置的集合元素。List 集合默認按元素的添加順序設置元素的索引,第一個添加到 List 集合中的元素的索引爲 0,第二個爲 1,依此類推。

List 實現了 Collection 接口,在List中最常用的兩個類就數ArrayList和LinkedList。他們兩個的實現代表着數據結構中的兩種典型:線性表和鏈表。在這裏,這個線性表是可以根據需要自動增長的。Java的庫裏面默認沒有實現單鏈表,LinkedList實際上是一個雙鏈表。

11.3.2 ArrayList

1)介紹
ArrayList是內部基於數組的線性表實現,使用transient Object[] elementData; // non-private to simplify nested class access數組變量來保存元素,使用private int size;來保存當前list的長度。

ArrayList 類實現了可變數組的大小,存儲在內的數據稱爲元素。它還提供了快速基於索引訪問元素的方式,對尾部成員的增加和刪除支持較好。使用 ArrayList 創建的集合,允許對集合中的元素進行快速的隨機訪問,不過,向 ArrayList 中插入與刪除元素的速度相對較慢。
2)構造方法
ArrayList 類的常用構造方法有如下形式:

  • ArrayList() 構造一個初始容量爲10的空列表。
  • ArrayList(Collection<? extends E> c) 構造一個列表,其中包含指定集合的元素,按集合的迭代器返回元素的順序排列。
  • ArrayList(int initialCapacity) 構造具有指定初始容量的空列表。

3)常用方法(增加、刪除、其他)
3.1 增加元素

  • boolean add(E e) 將指定的元素附加到此列表的末尾。
  • void add(int index, E element) 將指定元素插入到列表中的指定位置。
    +boolean addAll(Collection<? extends E> c) 將指定集合中的所有元素按照指定集合的迭代器返回它們的順序,追加到此列表的末尾。
  • boolean addAll(int index, Collection<? extends E> c) 從指定位置開始,將指定集合中的所有元素插入此列表。

3.2)刪除元素

  • public E remove(int index) :刪除單個元素
  • public boolean remove(Object o) :刪除單個元素
  • protected void removeRange(int fromIndex, int toIndex):刪除區段內元素
  • public boolean removeAll(Collection<?> c):刪除集合中的共同元素
  • public void clear():刪除所有元素

3.3)其他常用方法

  • E get(int index) 獲取此集合中指定索引位置的元素,E 爲集合中元素的數據類型
  • int index(Object o) 返回此集合中第一次出現指定元素的索引,如果此集合不包含該元
    素,則返回 -1
  • int lastIndexOf(Object o) 返回此集合中最後一次出現指定元素的索引,如果此集合不包含該
    元素,則返回 -1
  • E set(int index, Eelement) 將此集合中指定索引位置的元素修改爲 element 參數指定的對象。
    此方法返回此集合中指定索引位置的原元素
  • List subList(int fromlndex, int tolndex) 返回一個新的集合,新集合中包含 fromlndex 和 tolndex 索引之間的所有元素。包含 fromlndex 處的元素,不包含 tolndex 索引處的
    元素

11.3.3 LinkedList

1)介紹
LinkedList 同時實現了List< E >Deque< E >兩個接口,所以也滿足先進先出的隊列特性。此外LinkedList還提供了棧的特性。
LinkedList 類採用鏈表結構保存對象,這種結構的優點是便於向集合中插入或者刪除元素。需要頻繁向集合中插入和刪除元素時,使用 LinkedList 類比 ArrayList 類效果高,但是 LinkedList 類隨機訪問元素的速度則相對較慢。這裏的隨機訪問是指檢索集合中特定索引位置的元素。

2)構造方法

  • LinkedList()
    構造一個空列表。
  • LinkedList(Collection<? extends E> c)
    構造一個列表,其中包含指定集合的元素,按集合的迭代器返回元素的順序排列。

3)常用方法
LinkedList 類除了包含 Collection 接口和 List 接口中的所有方法之外,還特別提供了一下方法。

方法名稱 說明
void addFirst(E e) 將指定元素添加到此集合的開頭
void addLast(E e) 將指定元素添加到此集合的末尾
E getFirst() 返回此集合的第一個元素
E getLast() 返回此集合的最後一個元素
E removeFirst() 刪除此集合中的第一個元素
E removeLast() 刪除此集合中的最後一個元素

4)與ArrayList的區別
ArrayList 是基於動態數組數據結構的實現,訪問元素速度優於 LinkedList。LinkedList 是基於鏈表數據結構的實現,佔用的內存空間比較大,但在批量插入或刪除數據時優於 ArrayList。

5)棧的特性
另外,LinkedList還有一個有意思的特性,它本身也實現了一個棧的規範。它採用每次直接在鏈表頭之前添加元素來實現push方法,刪除鏈表頭元素實現pop方法。和棧實現相關的方法實現如下:

public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
 }
public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
}
public void push(E e) {
        addFirst(e);
}
public E pop() {
        return removeFirst();
}

這樣,以後我們如果要使用棧的話,除了聲明Stack類以外,把LinkedList當成Stack使也是可行的。

11.3.4 Vector

1)簡介
Vector類實現了一個可增長的對象數組。與數組一樣,它包含可以使用整數索引訪問的組件。
但是,向量的大小可以根據需要增長或收縮,以適應在創建向量之後添加和刪除項。每個向量都試圖通過維護容量和電容增量來優化存儲管理。容量總是至少與向量大小相同;它通常更大,因爲隨着組件被添加到向量中,向量的存儲以塊的形式增加應用程序可以在插入大量組件之前增加向量的容量;
這減少了增量重新分配的數量。

從Java 2平臺v1.2開始,對該類進行了改進以實現List接口,使其成爲Java集合框架的成員。
與新的集合實現不同,Vector是同步的。

如果不需要線程安全的實現,建議使用ArrayList代替Vector。

2)構造方法

  • Vector()
    構造一個空向量,使其內部數據數組大小爲10,標準容量增量爲0。
  • Vector(Collection<? extends E> c)
    構造一個空向量,使其內部數據數組大小爲10,標準容量增量爲0。
  • Vector(int initialCapacity)
    構造一個具有指定初始容量且其容量增量爲零的空向量。
  • Vector(int initialCapacity, int capacityIncrement)
    構造具有指定初始容量和容量增量的空向量。

3)常用方法
與ArrayList的方法用法非常相似

11.4 Set類(HashSet 類和 TreeSet類)

11.4.1 簡介

Set 集合類似於一個罐子,程序可以依次把多個對象“丟進”Set 集合,而 Set 集合通常不能記住元素的添加順序。也就是說 Set 集合中的對象不按特定的方式排序,只是簡單地把對象加入集合。Set 集合中不能包含重複的對象,並且最多隻允許包含一個 null 元素

Set 實現了 Collection 接口,它主要有兩個常用的實現類:HashSet 類和 TreeSet類。

11.4.2 HashSet類

1)簡介
HashSet 是 Set 接口的典型實現,大多數時候使用 Set 集合時就是使用這個實現類。HashSet 是按照 Hash 算法來存儲集合中的元素。因此具有很好的存取和查找性能。

HashSet 具有以下特點:

  • 不能保證元素的排列順序,順序可能與添加順序不同,順序也有可能發生變化。
  • HashSet 不是同步的,如果多個線程同時訪問或修改一個 HashSet,則必須通過代碼來保證其同步。
  • 集合元素值可以是 null。

當向 HashSet 集合中存入一個元素時,HashSet 會調用該對象的 hashCode() 方法來得到該對象的 hashCode 值,然後根據該 hashCode 值決定該對象在 HashSet 中的存儲位置。如果有兩個元素通過 equals() 方法比較返回的結果爲 true,但它們的 hashCode 不相等,HashSet 將會把它們存儲在不同的位置,依然可以添加成功。
也就是說,兩個對象的 hashCode 值相等且通過 equals() 方法比較返回結果爲 true,則 HashSet 集合認爲兩個元素相等。
注意:如果向 Set 集合中添加兩個相同的元素,則後添加的會覆蓋前面添加的元素,即在 Set 集合中不會出現相同的元素。
2)構造方法

  • HashSet()
    構造一個新的空集;支持的HashMap實例具有默認的初始容量(16)和負載因子(0.75)。
  • HashSet(Collection<? extends E> c)
    構造一個新集合,該集合包含指定集合中的元素。
  • HashSet(int initialCapacity)
    構造一個新的空集;支持的HashMap實例具有指定的初始容量和缺省負載因子(0.75)。
  • HashSet(int initialCapacity, float loadFactor)
    構造一個新的空集;支持的HashMap實例具有指定的初始容量和指定的負載因子。

3)常用方法
在 HashSet 類中實現了 Collection 接口中的所有方法。常用的就是Collection接口裏的方法。

11.4.3 TreeSet類

1)簡介
TreeSet 類同時實現了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以實現對集合進行自然排序,因此使用 TreeSet 類實現的 Set 接口默認情況下是自然排序的,這裏的自然排序指的是升序排序。

TreeSet 只能對實現了 Comparable 接口的類對象進行排序,因爲 Comparable 接口中有一個 compareTo(Object o) 方法用於比較兩個對象的大小。例如 a.compareTo(b),如果 a 和 b 相等,則該方法返回 0;如果 a 大於 b,則該方法返回大於 0 的值;如果 a 小於 b,則該方法返回小於 0 的值。

2)實現Comparable接口類對象的比較方式

比較方式
包裝類(BigDecimal、Biglnteger、 Byte、Double、Float、Integer、Long 及 Short) 按數字大小比較
Character 按字符的 Unicode 值的數字大小比較
String 按字符串中字符的 Unicode 值的數字大小比較

3)構造方法

  • TreeSet()
    構造一個新的空樹集,根據其元素的自然順序排序。
  • TreeSet(Collection<? extends E> c)
    構造一個新樹集,其中包含指定集合中的元素,按照其元素的自然順序排序。
  • TreeSet(Comparator<? super E> comparator)
    構造一個新的空樹集,根據指定的比較器排序。
  • TreeSet(SortedSet s)
    構造一個包含相同元素的新樹集,並使用與指定的已排序集相同的順序。

4)常用方法
TreeSet 類除了實現 Collection 接口的所有方法之外,還提供如下的方法。

方法名稱 說明
E first() 返回此集合中的第一個元素。其中,E 表示集合中元素的數據類型
E last() 返回此集合中的最後一個元素
E poolFirst() 獲取並移除此集合中的第一個元素
E poolLast() 獲取並移除此集合中的最後一個元素
SortedSet subSet(E fromElement,E toElement) 返回一個新的集合,新集合包含原集合中 fromElement 對象與 toElement對象之間的所有對象。包含 fromElement 對象,不包含 toElement 對象
SortedSet headSet<E toElement〉 返回一個新的集合,新集合包含原集合中 toElement 對象之前的所有對象。不包含 toElement 對象
SortedSet tailSet(E fromElement) 返回一個新的集合,新集合包含原集合中 fromElement 對象之後的所有對象。包含 fromElement 對象

5)注意
在使用自然排序時只能向 TreeSet 集合中添加相同數據類型的對象,否則會拋出 ClassCastException 異常。如果向 TreeSet 集合中添加了一個 Double 類型的對象,則後面只能添加 Double 對象,不能再添加其他類型的對象,例如 String 對象等。

11.5 Map集合及其遍歷

11.5.1 簡介

Map 是一種鍵-值對(key-value)集合,Map 集合中的每一個元素都包含一個鍵(key)對象和一個值(value)對象。用於保存具有映射關係的數據。Map 集合裏保存着兩組值,一組值用於保存 Map 裏的 key,另外一組值用於保存 Map 裏的 value,key 和 value 都可以是任何引用類型的數據。Map 的 key 不允許重複,value 可以重複,即同一個 Map 對象的任何兩個 key 通過 equals 方法比較總是返回 false。

Map 中的 key 和 value 之間存在單向一對一關係,即通過指定的 key,總能找到唯一的、確定的 value。從 Map 中取出數據時,只要給出指定的 key,就可以取出對應的 value。

Map 接口主要有兩個實現類:HashMap 類和 TreeMap 類。其中,HashMap 類按哈希算法來存取鍵對象,而 TreeMap 類可以對鍵對象進行排序。
Map接口實現的方法

方法名稱 說明
void clear() 刪除該 Map 對象中的所有 key-value 對。
boolean containsKey(Object key) 查詢 Map 中是否包含指定的 key,如果包含則返回 true。
boolean containsValue(Object value) 查詢 Map 中是否包含一個或多個 value,如果包含則返回 true。
V get(Object key) 返回 Map 集合中指定鍵對象所對應的值。V 表示值的數據類型V put(K key, V value) 向 Map 集合中添加鍵-值對,如果當前 Map 中已有一個與該 key 相等的 key-value 對,則新的 key-value 對會覆蓋原來的 key-value 對。
void putAll(Map m) 將指定 Map 中的 key-value 對複製到本 Map 中。
V remove(Object key) 從 Map 集合中刪除 key 對應的鍵-值對,返回 key 對應的 value,如果該 key 不存在,則返回 null
boolean remove(Object key, Object value) 這是 Java 8 新增的方法,刪除指定 key、value 所對應的 key-value 對。如果從該 Map 中成功地刪除該 key-value 對,該方法返回 true,否則返回 false。
Set entrySet() 返回 Map 集合中所有鍵-值對的 Set 集合,此 Set 集合中元素的數據類型爲 Map.Entry
Set keySet() 返回 Map 集合中所有鍵對象的 Set 集合
boolean isEmpty() 查詢該 Map 是否爲空(即不包含任何 key-value 對),如果爲空則返回 true。
int size() 返回該 Map 裏 key-value 對的個數
Collection values() 返回該 Map 裏所有 value 組成的 Collection

11.5.2 HashMap類

1)簡介
基於哈希表實現的映射接口。此實現提供所有可選的映射操作,並允許空值和空鍵。(HashMap類大致相當於Hashtable,只是它是不同步的,並且允許爲空。)該類不保證映射的順序;特別是,它不能保證訂單在一段時間內保持不變。

HashMap實例有兩個影響其性能的參數:初始容量和負載因子。
容量是哈希表中的桶數,初始容量只是創建哈希表時的容量。負載因子是一個度量哈希表在其容量自動增加之前允許的滿度的度量。當哈希表中的條目數超過負載因子和當前容量的乘積時,將對哈希表進行重新哈希(即重新構建內部數據結構),使哈希表的桶數大約是桶數的兩倍。一般來說,默認的負載因子(.75)在時間和空間成本之間提供了很好的權衡。更高的值減少了空間開銷,但增加了查找成本(反映在HashMap類的大多數操作中,包括get和put)。在設置map的初始容量時,應該考慮map中條目的期望數量及其負載因子,從而最小化rehash操作的數量。

注意,使用具有相同hashCode()的多個鍵肯定會降低任何散列表的性能。爲了改善影響,當鍵是可比較的,這個類可以使用鍵之間的比較順序來幫助打破聯繫。

注意,這個實現不是同步的。如果多個線程同時訪問一個散列映射,並且至少有一個線程從結構上修改了該映射,則必須在外部對其進行同步。

2)構造函數

  • HashMap()
    使用默認初始容量(16)和默認負載因子(0.75)構造一個空HashMap。
  • HashMap(int initialCapacity)
    構造一個具有指定初始容量和缺省負載因子(0.75)的空HashMap。
  • HashMap(int initialCapacity, float loadFactor)
    構造具有指定初始容量和負載因子的空HashMap。
  • HashMap(Map<? extends K,? extends V> m)
    使用與指定映射相同的映射構造新的HashMap。

3)常用方法
參考java8開發手冊

11.5.3 TreeMap類

1)簡介
一個基於紅黑樹的NavigableMap實現。映射根據其鍵的自然順序進行排序,或者根據使用的構造函數由創建映射時提供的比較器進行排序。這個實現爲containsKey、get、put和remove操作提供了保證的log(n)時間成本。算法是對Cormen, Leiserson和Rivest介紹的算法的改編。

注意,這個實現不是同步的。
如果多個線程同時訪問一個映射,並且至少有一個線程在結構上修改了映射,那麼它必須在外部同步。(結構修改是指任何增加或刪除一個或多個映射的操作;僅更改與現有鍵關聯的值不是結構修改)。這通常是通過在一些自然封裝了映射的對象上進行同步來實現的。如果不存在這樣的對象,則應該使用集合“包裝”映射。

因此,在面對併發修改時,迭代器會快速而乾淨地失敗,而不是在將來某個不確定的時間冒任意的、不確定的行爲的風險。

注意,不能保證迭代器的快速故障行爲,因爲通常來說,在存在非同步併發修改的情況下,不可能做出任何嚴格的保證。故障快速迭代器在最大努力的基礎上拋出ConcurrentModificationException。
因此,編寫一個依賴於這個異常的正確性的程序是錯誤的:迭代器的快速故障行爲應該只用於檢測bug。
注意,可以使用put更改關聯映射中的映射。
2)構造方法

  • TreeMap()
    使用鍵的自然順序構造一個新的空樹映射。
  • TreeMap(Comparator<? super K> comparator)
    構造一個新的、空的樹映射,根據給定的比較器排序。
  • TreeMap(Map<? extends K,? extends V> m)
    構造一個新的樹映射,其中包含與給定映射相同的映射,並根據其鍵的自然順序排序。
  • TreeMap(SortedMap<K,? extends V> m)
    構造一個包含相同映射的新樹映射,並使用與指定排序映射相同的順序。

3)常用方法
參考java開發手冊

11.5.4 Map集合的遍歷(4種方法)

Map 集合的遍歷與 List 和 Set 集合不同。Map 有兩組值,因此遍歷時可以只遍歷值的集合,也可以只遍歷鍵的集合,也可以同時遍歷。Map 以及實現 Map 的接口類(如 HashMap、TreeMap、LinkedHashMap、Hashtable 等)都可以用以下幾種方式遍歷。

1)在 for 循環中使用 entries 實現 Map 的遍歷(最常見和最常用的)。

public static void main(String[] args) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("入門教程1", "java");//使用put修改映射
    map.put("入門教程2", "c");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String mapKey = entry.getKey();
        String mapValue = entry.getValue();
        System.out.println(mapKey + ":" + mapValue);
    }
}

2)使用 for-each 循環遍歷 key 或者 values,一般適用於只需要 Map 中的 key 或者 value 時使用。性能上比 entrySet 較好。

public static void main(String[] args) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("入門教程1", "java");//使用put修改映射
    map.put("入門教程2", "c");
	// 打印鍵集合
	for (String key : map.keySet()) {
  	  	System.out.println(key);
	}
	// 打印值集合
	for (String value : map.values()) {
    	System.out.println(value);
	}
}

3)使用迭代器(Iterator)遍歷

public static void main(String[] args) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("入門教程1", "java");//使用put修改映射
    map.put("入門教程2", "c");
	Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + ":" + value);
}
}

4)通過鍵找值遍歷,這種方式的效率比較低,因爲本身從鍵取值是耗時的操作。

for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
}

11.5.5 java8中 Map新增方法

Java 8 除了爲 Map 增加了 remove(Object key, Object value) 默認方法之外,還增加了如下方法。

名稱 說明
Object compute(Object key, BiFunction remappingFunction) 該方法使用 remappingFunction 根據原 key-value 對計算一個新 value。只要新 value 不爲 null,就使用新 value 覆蓋原 value;如果原 value 不爲 null,但新 value 爲 null,則刪除原 key-value 對;如果原 value、新 value 同時爲 null,那麼該方法不改變任何 key-value 對,直接返回 null。
Object computeIfAbsent(Object key, Function mappingFunction) 如果傳給該方法的 key 參數在 Map 中對應的 value 爲 null,則使用 mappingFunction 根據 key 計算一個新的結果,如果計算結果不爲 null,則用計算結果覆蓋原有的 value。如果原 Map 原來不包括該 key,那麼該方法可能會添加一組 key-value 對。
Object computeIfPresent(Object key, BiFunction remappingFunction) 如果傳給該方法的 key 參數在 Map 中對應的 value 不爲 null,該方法將使用 remappingFunction 根據原 key、value 計算一個新的結果,如果計算結果不爲 null,則使用該結果覆蓋原來的 value;如果計算結果爲 null,則刪除原 key-value 對。
void forEach(BiConsumer action) 該方法是 Java 8 爲 Map 新增的一個遍歷 key-value 對的方法,通過該方法可以更簡潔地遍歷 Map 的 key-value 對。
Object getOrDefault(Object key, V defaultValue) 獲取指定 key 對應的 value。如果該 key 不存在,則返回 defaultValue。
Object merge(Object key, Object value, BiFunction remappingFunction) 該方法會先根據 key 參數獲取該 Map 中對應的 value。如果獲取的 value 爲 null,則直接用傳入的 value 覆蓋原有的 value(在這種情況下,可能要添加一組 key-value 對);如果獲取的 value 不爲 null,則使用 remappingFunction 函數根據原 value、新 value 計算一個新的結果,並用得到的結果去覆蓋原有的 value。
Object putIfAbsent(Object key, Object value) 該方法會自動檢測指定 key 對應的 value 是否爲 null,如果該 key 對應的 value 爲 null,該方法將會用新 value 代替原來的 null 值。
Object replace(Object key, Object value) 將 Map 中指定 key 對應的 value 替換成新 value。與傳統 put() 方法不同的是,該方法不可能添加新的 key-value 對。如果嘗試替換的 key 在原 Map 中不存在,該方法不會添加 key-value 對,而是返回 null。
boolean replace(K key, V oldValue, V newValue) 將 Map 中指定 key-value 對的原 value 替換成新 value。如果在 Map 中找到指定的 key-value 對,則執行替換並返回 true,否則返回 false。
replaceAll(BiFunction function) 該方法使用 BiFunction 對原 key-value 對執行計算,並將計算結果作爲該 key-value 對的 value 值。

11.5.6 如何創建不可變集合(of(),Map.Entry()顯示使用)

Java 9 版本以前,假如要創建一個包含 6 個元素的 Set 集合,程序需要先創建 Set 集合,然後調用 6 次 add() 方法向 Set 集合中添加元素。Java 9 對此進行了簡化,程序直接調用 Set、List、Map 的 of() 方法即可創建包含 N 個元素的不可變集合,這樣一行代碼就可創建包含 N 個元素的集合。
創建不可變的 Map 集合有兩個方法。使用 of() 方法時只要依次傳入多個 key-value 對即可;還可使用 ofEntries() 方法,該方法可接受多個 Entry 對象,因此程序顯式使用 Map.entry() 方法來創建 Map.Entry 對象。
Test


		// 創建包含4個元素的Set集合
		Set set1 = Set.add("1").add("2").add("3").add("4");
        Set set2 = Set.of("1", "2", "3", "4");
    
       // 使用Map.entry()方法顯式構建key-value對
       Map map = Map.ofEntries(Map.entry("語文", 89), Map.entry("數學", 82), Map.entry("英語", 92));

11.6 Collections工具類

11.6.1 簡介

Collections 類是 Java 提供的一個操作 Set、List 和 Map 等集合的工具類。Collections 類提供了許多操作集合的靜態方法,藉助這些靜態方法可以實現集合元素的排序、查找替換和複製等操作。下面介紹 Collections 類中操作集合的常用方法。

11.6.2 常用方法

1)排序(正向和逆向)
Collections 提供瞭如下方法用於對 List 集合元素進行排序。

  • void reverse(List list):對指定 List 集合元素進行逆向排序。
  • void shuffle(List list):對 List 集合元素進行隨機排序(shuffle 方法模擬了“洗牌”動作)。
  • void sort(List list):根據元素的自然順序對指定 List 集合的元素按升序進行排序。
  • void sort(List list, Comparator c):根據指定 Comparator 產生的順序對 List 集合元素進行排序。
  • void swap(List list, int i, int j):將指定 List 集合中的 i 處元素和 j 處元素進行交換。
  • void rotate(List list, int distance):當 distance 爲正數時,將 list 集合的後 distance 個元素“整體”移到前面;當 distance 爲負數時,將 list 集合的前 distance 個元素“整體”移到後面。該方法不會改變集合的長度

例子

public class Test1 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        List prices = new ArrayList();
        for (int i = 0; i < 5; i++) {
            System.out.println("請輸入第 " + (i + 1) + " 個商品的價格:");
            int p = input.nextInt();
            prices.add(Integer.valueOf(p)); // 將錄入的價格保存到List集合中
        }
        Collections.sort(prices); // 調用sort()方法對集合進行排序,因爲是靜態方法,使用類名訪問
        System.out.println("價格從低到高的排列爲:");
        for (int i = 0; i < prices.size(); i++) {
            System.out.print(prices.get(i) + "\t");
        }
    }
}

2)查找、替換操作
Collections 還提供瞭如下常用的用於查找、替換集合元素的方法。

  • int binarySearch(List list, Object key):使用二分搜索法搜索指定的 List 集合,以獲得指定對象在 List 集合中的索引。如果要使該方法可以正常工作,則必須保證 List 中的元素已經處於有序狀態。
  • Object max(Collection coll):根據元素的自然順序,返回給定集合中的最大元素。
  • Object max(Collection coll, Comparator comp):根據 Comparator 指定的順序,返回給定集合中的最大元素。
  • Object min(Collection coll):根據元素的自然順序,返回給定集合中的最小元素。
  • Object min(Collection coll, Comparator comp):根據 Comparator 指定的順序,返回給定集合中的最小元素。
  • void fill(List list, Object obj):使用指定元素 obj 替換指定 List 集合中的所有元素。
  • int frequency(Collection c, Object o):返回指定集合中指定元素的出現次數。
  • int indexOfSubList(List source, List target):返回子 List 對象在父 List 對象中第一次出現的位置索引;如果父 List 中沒有出現這樣的子 List,則返回 -1。
  • int lastIndexOfSubList(List source, List target):返回子 List 對象在父 List 對象中最後一次出現的位置索引;如果父 List 中沒有岀現這樣的子 List,則返回 -1。
  • boolean replaceAll(List list, Object oldVal, Object newVal):使用一個新值 newVal 替換 List 對象的所有舊值 oldVal。

3)複製
Collections 類的 copy() 靜態方法用於將指定集合中的所有元素複製到另一個集合中。執行 copy() 方法後,目標集合中每個已複製元素的索引將等同於源集合中該元素的索引。
copy() 方法的語法格式如下:

void copy(List <? super T> dest,List<? extends T> src)

其中,dest 表示目標集合對象,src 表示源集合對象。

11.6.3 遍歷Collection的方法(4種)

1)使用 Lambda 表達式來遍歷集合元素
Java 8 爲 Iterable 接口新增了一個 forEach(Consumer action) 默認方法,該方法所需參數的類型是一個函數式接口,而 Iterable 接口是 Collection 接口的父接口,因此 Collection 集合也可直接調用該方法。

當程序調用 Iterable 的 forEach(Consumer action) 遍歷集合元素時,程序會依次將集合元素傳給 Consumer 的 accept(T t) 方法(該接口中唯一的抽象方法)。正因爲 Consumer 是函數式接口,因此可以使用 Lambda 表達式來遍歷集合元素。

如下程序示範了使用 Lambda 表達式來遍歷集合元素。

public class CollectionEach {
    public static void main(String[] args) {
        // 創建一個集合
        Collection objs = new HashSet();
        objs.add("1");
        objs.add("2");
        objs.add("3");//add是Collection接口的通用方法
        // 調用forEach()方法遍歷集合
        objs.forEach(obj -> System.out.println("迭代集合元素:" + obj));
    }
}

2)通過迭代器遍歷Collection集合的元素
Iterator(迭代器)是一個接口,它的作用就是遍歷容器的所有元素,也是 Java 集合框架的成員,但它與 Collection 和 Map 系列的集合不一樣,Collection 和 Map 系列集合主要用於盛裝其他對象,而 Iterator 則主要用於遍歷(即迭代訪問)Collection 集合中的元素。

Iterator 接口隱藏了各種 Collection 實現類的底層細節,嚮應用程序提供了遍歷 Collection 集合元素的統一編程接口。Iterator 接口裏定義瞭如下 4 個方法。

  • boolean hasNext():如果被迭代的集合元素還沒有被遍歷完,則返回 true。
  • Object next():返回集合裏的下一個元素。
  • void remove():刪除集合裏上一次 next 方法返回的元素。
  • void forEachRemaining(Consumer action):這是 Java 8 爲 Iterator 新增的默認方法,該方法可使用 Lambda 表達式來遍歷集合元素。
	 Iterator it = objs.iterator();
        while (it.hasNext()) {
            // it.next()方法返回的數據類型是Object類型,因此需要強制類型轉換
            String obj = (String) it.next();
            System.out.println(obj);
            }

Iterator 必須依附於 Collection 對象,若有一個 Iterator 對象,則必然有一個與之關聯的 Collection 對象。Iterator 提供了兩個方法來迭代訪問 Collection 集合裏的元素,並可通過 remove() 方法來刪除集合中上一次 next() 方法返回的集合元素。

3)使用Lambda表達式遍歷Iterator迭代器
Java 8 爲 Iterator 引入了一個 forEachRemaining(Consumer action) 默認方法,該方法所需的 Consumer 參數同樣也是函數式接口。當程序調用 Iterator 的 forEachRemaining(Consumer action) 遍歷集合元素時,程序會依次將集合元素傳給 Consumer 的 accept(T t) 方法(該接口中唯一的抽象方法)。

 // 獲取objs集合對應的迭代器
        Iterator it = objs.iterator();
        // 使用Lambda表達式(目標類型是Comsumer)來遍歷集合元素
        it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));

調用了 Iterator 的 forEachRemaining() 方法來遍歷集合元素,傳給該方法的參數是一個 Lambda 表達式,該 Lambda 表達式的目標類型是 Consumer,因此上面代碼也可用於遍歷集合元素。

4)使用foreach循環遍歷Collection集合
我們還可以使用 Java 5 提供的 foreach 循環迭代訪問集合元素,而且更加便捷。

	for (Object obj : objs) {
            // 此處的obj變量也不是集合元素本身
            String obj1 = (String) obj;
            System.out.println(obj1);
            }

注意只要迭代訪問集合元素時,該集合不能被改變,否則將引發 ConcurrentModificationException 異常。上面4種方式都不能改變集合的元素

11.6.4 使用Java 8新增的Predicate操作Collection集合

Java 8 起爲 Collection 集合新增了一個 removeIf(Predicate filter) 方法,該方法將會批量刪除符合 filter 條件的所有元素。該方法需要一個 Predicate 對象作爲參數,Predicate 也是函數式接口,因此可使用 Lambda 表達式作爲參數。
依然是最上面的那個程序

public class CollectionEach {
    public static void main(String[] args) {
        // 創建一個集合
        Collection objs = new HashSet();
        objs.add("1fsgs");
        objs.add("2dsdgdgs");
        objs.add("3dgsdgsdfg");//add是Collection接口的通用方法
 // 使用Lambda表達式(目標類型是Predicate)過濾集合
        objs.removeIf(ele -> ((String) ele).length() < 12);
        //程序傳入一個 Lambda 表達式作爲過濾條件。所有長度小於 12 的字符串元素都會被刪除。
        System.out.println(objs);
   }

11.6.5 使用Java 8新增的Stream操作Collection集合

1)簡介
Java 8 新增了 Stream、IntStream、LongStream、DoubleStream 等流式 API,這些 API 代表多個支持串行和並行聚集操作的元素。上面 4 個接口中,Stream 是一個通用的流接口,而 IntStream、LongStream、 DoubleStream 則代表元素類型爲 int、long、double 的流。

Java 8 還爲上面每個流式 API 提供了對應的 Builder,例如 Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder,開發者可以通過這些 Builder 來創建對應的流。

獨立使用 Stream 的步驟如下:

  1. 使用 Stream 或 XxxStream 的 builder() 類方法創建該 Stream 對應的 Builder。
  2. 重複調用 Builder 的 add() 方法向該流中添加多個元素。
  3. 調用 Builder 的 build() 方法獲取對應的 Stream。
  4. 調用 Stream 的聚集方法。

2)Test

package collectionandmap;
/**
 * 目的:演示Stream的使用
 *
 * 獨立使用 Stream 的步驟如下:
 * 使用 Stream 或 XxxStream 的 builder() 類方法創建該 Stream 對應的 Builder。
 * 重複調用 Builder 的 add() 方法向該流中添加多個元素。
 * 調用 Builder 的 build() 方法獲取對應的 Stream。
 * 調用 Stream 的聚集方法。
 *
 * 注意事項:
 * Stream 提供了大量的方法進行聚集操作,這些方法既可以是“中間的”(intermediate),也可以是 "末端的"(terminal)。
 * 中間方法:中間操作允許流保持打開狀態,並允許直接調用後續方法。上面程序中的 map() 方法就是中間方法。中間方法的返回值是另外一個流。
 * 末端方法:末端方法是對流的最終操作。當對某個 Stream 執行末端方法後,該流將會被“消耗”且不再可用。 sum()、count()、average() 等方法都是末端方法。
 */


import java.util.stream.IntStream;

public class IntStreamTest {
        public static void main(String[] args) {
            IntStream is = IntStream.builder().add(20).add(13).add(-2).add(18).build();
            // 下面調用聚集方法的代碼每次只能執行一行,都是末端方法
            // System.out.println("is 所有元素的最大值:" + is.max().getAsInt());
            //            System.out.println("is 所有元素的最小值:" + is.min().getAsInt());
            //            System.out.println("is 所有元素的總和:" + is.sum());
            //            System.out.println("is 所有元素的總數:" + is.count());
            //            System.out.println("is 所有元素的平均值:" + is.average());
            //            System.out.println("is所有元素的平方是否都大於20: " + is.allMatch(ele -> ele * ele > 20));
            //            System.out.println("is是否包含任何元素的平方大於20 : " + is.anyMatch(ele -> ele * ele > 20));



            // 將is映射成一個新Stream,新Stream的每個元素是原Stream元素的2倍+1,中端方法
            IntStream newIs = is.map(ele -> ele * 2 + 1);
            // 使用方法引用的方式來遍歷集合元素
            newIs.forEach(System.out::println); // 輸岀 41 27 -3 37
        }
    }

在這裏插入圖片描述

Stream 提供了大量的方法進行聚集操作,這些方法既可以是“中間的”(intermediate),也可以是 “末端的”(terminal)。

  • 中間方法:中間操作允許流保持打開狀態,並允許直接調用後續方法。上面程序中的 map() 方法就是中間方法。中間方法的返回值是另外一個流。
  • 末端方法:末端方法是對流的最終操作。當對某個 Stream 執行末端方法後,該流將會被“消耗”且不再可用。上面程序中的 sum()、count()、average() 等方法都是末端方法。

除此之外,關於流的方法還有如下兩個特徵。

  • 有狀態的方法:這種方法會給流增加一些新的屬性,比如元素的唯一性、元素的最大數量、保證元素以排序的方式被處理等。有狀態的方法往往需要更大的性能開銷。
  • 短路方法:短路方法可以儘早結束對流的操作,不必檢查所有的元素。

3)Stream常用中間方法

方法 說明
filter(Predicate predicate) 過濾 Stream 中所有不符合 predicate 的元素
mapToXxx(ToXxxFunction mapper) 使用 ToXxxFunction 對流中的元素執行一對一的轉換,該方法返回的新流中包含了 ToXxxFunction 轉換生成的所有元素。
peek(Consumer action) 依次對每個元素執行一些操作,該方法返回的流與原有流包含相同的元素。該方法主要用於調試。
distinct() 該方法用於排序流中所有重複的元素(判斷元素重複的標準是使用 equals() 比較返回 true)。這是一個有狀態的方法。
sorted() 該方法用於保證流中的元素在後續的訪問中處於有序狀態。這是一個有狀態的方法。
limit(long maxSize) 該方法用於保證對該流的後續訪問中最大允許訪問的元素個數。這是一個有狀態的、短路方法。

4)Stream 常用的末端方法。

方法 說明
forEach(Consumer action) 遍歷流中所有元素,對每個元素執行action
toArray() 將流中所有元素轉換爲一個數組
reduce() 該方法有三個重載的版本,都用於通過某種操作來合併流中的元素
min() 返回流中所有元素的最小值
max() 返回流中所有元素的最大值
count() 返回流中所有元素的數量
anyMatch(Predicate predicate) 判斷流中是否至少包含一個元素符合 Predicate 條件。
allMatch(Predicate predicate) 判斷流中是否每個元素都符合 Predicate 條件
noneMatch(Predicate predicate) 判斷流中是否所有元素都不符合 Predicate 條件
findFirst() 返回流中的第一個元素
findAny() 返回流中的任意一個元素
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章