1. 程式人生 > 實用技巧 >《On Java 8》讀書筆記012_集合

《On Java 8》讀書筆記012_集合

https://github.com/LingCoder/OnJava8

集合

java.util 庫提供了一套相當完整的集合類(collection classes)來解決這個問題,其中基本的型別有 List 、 Set 、 Queue 和 Map。這些型別也被稱作容器類(container classes),但我將使用Java類庫使用的術語。集合提供了完善的方法來儲存物件,可以使用這些工具來解決大量的問題。

泛型和型別安全的集合

使用 Java 5 之前的集合的一個主要問題是編譯器允許你向集合中插入不正確的型別。例如,考慮一個 Apple 物件的集合,這裡使用最基本最可靠的 ArrayList 。現在,可以把 ArrayList 看作“可以自動擴充自身尺寸的陣列”來看待。使用 ArrayList 相當簡單:建立一個例項,用 add() 插入物件;然後用 get() 來訪問這些物件,此時需要使用索引,就像陣列那樣,但是不需要方括號。^2 ArrayList 還有一個 size() 方法,來說明集合中包含了多少個元素,所以不會不小心因陣列越界而引發錯誤(通過丟擲執行時異常,異常章節介紹了異常)。

在本例中, Apple 和 Orange 都被放到了集合中,然後將它們取出。正常情況下,Java編譯器會給出警告,因為這個示例沒有使用泛型。在這裡,使用特定的註解來抑制警告資訊。註解以“@”符號開頭,可以帶引數。這裡的 @SuppressWarning 註解及其引數表示只抑制“unchecked”型別的警告(註解章節將介紹更多有關注解的資訊):

// collections/ApplesAndOrangesWithoutGenerics.java
// Simple collection use (suppressing compiler warnings)
// {ThrowsException}
import java.util.*;

class Apple {
  private static long counter;
  private final long id = counter++;
  public long id() { return id; }
}

class Orange {}

public class ApplesAndOrangesWithoutGenerics {
  @SuppressWarnings("unchecked")
  public static void main(String[] args) {
    ArrayList apples = new ArrayList();
    for(int i = 0; i < 3; i++)
      apples.add(new Apple());
    // No problem adding an Orange to apples:
    apples.add(new Orange());
    for(Object apple : apples) {
      ((Apple) apple).id();
      // Orange is detected only at run time
    }
  }
}
/* Output:
___[ Error Output ]___
Exception in thread "main"
java.lang.ClassCastException: Orange cannot be cast to
Apple
        at ApplesAndOrangesWithoutGenerics.main(ApplesA
ndOrangesWithoutGenerics.java:23)
*/

Apple 和 Orange 是截然不同的,它們除了都是 Object 之外沒有任何共同點(如果一個類沒有顯式地宣告繼承自哪個類,那麼它就自動繼承自 Object)。因為 ArrayList 儲存的是 Object ,所以不僅可以通過 ArrayList 的 add() 方法將 Apple 物件放入這個集合,而且可以放入 Orange 物件,這無論在編譯期還是執行時都不會有問題。當使用 ArrayList 的 get() 方法來取出你認為是 Apple 的物件時,得到的只是 Object 引用,必須將其轉型為 Apple。然後需要將整個表示式用括號括起來,以便在呼叫 Apple 的 id() 方法之前,強制執行轉型。否則,將會產生語法錯誤。

在執行時,當嘗試將 Orange 物件轉為 Apple 時,會出現輸出中顯示的錯誤。

在泛型章節中,你將瞭解到使用 Java 泛型來建立類可能很複雜。但是,使用預先定義的泛型類卻相當簡單。例如,要定義一個用於儲存 Apple 物件的 ArrayList ,只需要使用 ArrayList 來代替 ArrayList 。尖括號括起來的是型別引數(可能會有多個),它指定了這個集合例項可以儲存的型別。

通過使用泛型,就可以在編譯期防止將錯誤型別的物件放置到集合中。^3下面還是這個示例,但是使用了泛型:

// collections/ApplesAndOrangesWithGenerics.java
import java.util.*;

public class ApplesAndOrangesWithGenerics {
  public static void main(String[] args) {
    ArrayList<Apple> apples = new ArrayList<>();
    for(int i = 0; i < 3; i++)
      apples.add(new Apple());
    // Compile-time error:
    // apples.add(new Orange());
    for(Apple apple : apples) {
      System.out.println(apple.id());
    }
  }
}
/* Output:
0
1
2
*/

在 apples 定義的右側,可以看到 new ArrayList<>() 。這有時被稱為“菱形語法”(diamond syntax)。在 Java 7 之前,必須要在兩端都進行型別宣告,如下所示:

ArrayList<Apple> apples = new ArrayList<Apple>();

隨著型別變得越來越複雜,這種重複產生的程式碼非常混亂且難以閱讀。程式設計師發現所有型別資訊都可以從左側獲得,因此,編譯器沒有理由強迫右側再重複這些。雖然型別推斷(type inference)只是個很小的請求,Java 語言團隊仍然欣然接受並進行了改進。

有了 ArrayList 宣告中的型別指定,編譯器會阻止將 Orange 放入 apples ,因此,這會成為一個編譯期錯誤而不是執行時錯誤。

使用泛型,從 List 中獲取元素不需要強制型別轉換。因為 List 知道它持有什麼型別,因此當呼叫 get() 時,它會替你執行轉型。因此,使用泛型,你不僅知道編譯器將檢查放入集合的物件型別,而且在使用集合中的物件時也可以獲得更清晰的語法。

當指定了某個型別為泛型引數時,並不僅限於只能將確切型別的物件放入集合中。向上轉型也可以像作用於其他型別一樣作用於泛型。

Java集合類庫採用“持有物件”(holding objects)的思想,並將其分為兩個不同的概念,表示為類庫的基本介面:

集合(Collection) :一個獨立元素的序列,這些元素都服從一條或多條規則。List 必須以插入的順序儲存元素, Set 不能包含重複元素, Queue 按照排隊規則來確定物件產生的順序(通常與它們被插入的順序相同)。
對映(Map) : 一組成對的“鍵值對”物件,允許使用鍵來查詢值。 ArrayList 使用數字來查詢物件,因此在某種意義上講,它是將數字和物件關聯在一起。 map 允許我們使用一個物件來查詢另一個物件,它也被稱作關聯陣列(associative array),因為它將物件和其它物件關聯在一起;或者稱作字典(dictionary),因為可以使用一個鍵物件來查詢值物件,就像在字典中使用單詞查詢定義一樣。 Map 是強大的程式設計工具。
儘管並非總是可行,但在理想情況下,你編寫的大部分程式碼都在與這些介面打交道,並且唯一需要指定所使用的精確型別的地方就是在建立的時候。因此,可以像下面這樣建立一個 List :

List<Apple> apples = new ArrayList<>();

請注意, ArrayList 已經被向上轉型為了 List ,這與之前示例中的處理方式正好相反。使用介面的目的是,如果想要改變具體實現,只需在建立時修改它就行了,就像下面這樣:

List<Apple> apples = new LinkedList<>();

因此,應該建立一個具體類的物件,將其向上轉型為對應的介面,然後在其餘程式碼中都是用這個介面。

集合的列印

必須使用 Arrays.toString() 來生成陣列的可列印形式。但是列印集合無需任何幫助。下面是一個例子,這個例子中也介紹了基本的Java集合:

// collections/PrintingCollections.java
// Collections print themselves automatically
import java.util.*;

public class PrintingCollections {
  static Collection
  fill(Collection<String> collection) {
    collection.add("rat");
    collection.add("cat");
    collection.add("dog");
    collection.add("dog");
    return collection;
  }
  static Map fill(Map<String, String> map) {
    map.put("rat", "Fuzzy");
    map.put("cat", "Rags");
    map.put("dog", "Bosco");
    map.put("dog", "Spot");
    return map;
  }
  public static void main(String[] args) {
    System.out.println(fill(new ArrayList<>()));
    System.out.println(fill(new LinkedList<>()));
    System.out.println(fill(new HashSet<>()));
    System.out.println(fill(new TreeSet<>()));
    System.out.println(fill(new LinkedHashSet<>()));
    System.out.println(fill(new HashMap<>()));
    System.out.println(fill(new TreeMap<>()));
    System.out.println(fill(new LinkedHashMap<>()));
  }
}
/* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[rat, cat, dog]
[cat, dog, rat]
[rat, cat, dog]
{rat=Fuzzy, cat=Rags, dog=Spot}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}
*/

這顯示了Java集合庫中的兩個主要型別。它們的區別在於集合中的每個“槽”(slot)儲存的元素個數。 Collection 型別在每個槽中只能儲存一個元素。此類集合包括: List ,它以特定的順序儲存一組元素; Set ,其中元素不允許重複; Queue ,只能在集合一端插入物件,並從另一端移除物件(就本例而言,這只是檢視序列的另一種方式,因此並沒有顯示它)。 Map 在每個槽中存放了兩個元素,即鍵和與之關聯的值。

預設的列印行為,使用集合提供的 toString() 方法即可生成可讀性很好的結果。 Collection 打印出的內容用方括號括住,每個元素由逗號分隔。 Map 則由大括號括住,每個鍵和值用等號連線(鍵在左側,值在右側)。

第一個 fill() 方法適用於所有型別的 Collection ,這些型別都實現了 add() 方法以新增新元素。

ArrayList 和 LinkedList 都是 List 的型別,從輸出中可以看出,它們都按插入順序儲存元素。兩者之間的區別不僅在於執行某些型別的操作時的效能,而且 LinkedList 包含的操作多於 ArrayList 。本章後面將對這些內容進行更全面的探討。

HashSet , TreeSet 和 LinkedHashSet 是 Set 的型別。從輸出中可以看到, Set 僅儲存每個相同項中的一個,並且不同的 Set 實現儲存元素的方式也不同。 HashSet 使用相當複雜的方法儲存元素,這在附錄:集合主題中進行了探討。現在只需要知道,這種技術是檢索元素的最快方法,因此,儲存順序看上去沒有什麼意義(通常只關心某事物是否是 Set 的成員,而儲存順序並不重要)。如果儲存順序很重要,則可以使用 TreeSet ,它將按比較結果的升序儲存物件)或 LinkedHashSet ,它按照被新增的先後順序儲存物件。

Map (也稱為關聯陣列)使用鍵來查詢物件,就像一個簡單的資料庫。所關聯的物件稱為值。 假設有一個 Map 將美國州名與它們的首府聯絡在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作為鍵來查詢,幾乎就像使用陣列下標一樣。正是由於這種行為,對於每個鍵, Map 只儲存一次。

Map.put(key, value) 新增一個所想要新增的值並將它與一個鍵(用來查詢值)相關聯。 Map.get(key) 生成與該鍵相關聯的值。上面的示例僅新增鍵值對,並沒有執行查詢。這將在稍後展示。

請注意,這裡沒有指定(或考慮) Map 的大小,因為它會自動調整大小。 此外, Map 還知道如何列印自己,它會顯示相關聯的鍵和值。

本例使用了 Map 的三種基本風格: HashMap , TreeMap 和 LinkedHashMap 。

鍵和值儲存在 HashMap 中的順序不是插入順序,因為 HashMap 實現使用了非常快速的演算法來控制順序。 TreeMap 通過比較結果的升序來儲存鍵, LinkedHashMap 在保持 HashMap 查詢速度的同時按鍵的插入順序儲存鍵。

列表List

List承諾將元素儲存在特定的序列中。 List 介面在 Collection 的基礎上添加了許多方法,允許在 List 的中間插入和刪除元素。

有兩種型別的 List :

1、基本的 ArrayList ,擅長隨機訪問元素,但在 List 中間插入和刪除元素時速度較慢。
2、LinkedList ,它通過代價較低的在 List 中間進行的插入和刪除操作,提供了優化的順序訪問。 LinkedList 對於隨機訪問來說相對較慢,但它具有比 ArrayList 更大的特徵集。

迭代器Iterators

在任何集合中,都必須有某種方式可以插入元素並再次獲取它們。畢竟,儲存事物是集合最基本的工作。對於 List , add() 是插入元素的一種方式, get() 是獲取元素的一種方式。

如果從更高層次的角度考慮,會發現這裡有個缺點:要使用集合,必須對集合的確切型別程式設計。這一開始可能看起來不是很糟糕,但是考慮下面的情況:如果原本是對 List 編碼的,但是後來發現如果能夠將相同的程式碼應用於 Set 會更方便,此時應該怎麼做?或者假設想從一開始就編寫一段通用程式碼,它不知道或不關心它正在使用什麼型別的集合,因此它可以用於不同型別的集合,那麼如何才能不重寫程式碼就可以應用於不同型別的集合?

迭代器(也是一種設計模式)的概念實現了這種抽象。迭代器是一個物件,它在一個序列中移動並選擇該序列中的每個物件,而客戶端程式設計師不知道或不關心該序列的底層結構。另外,迭代器通常被稱為輕量級物件(lightweight object):建立它的代價小。因此,經常可以看到一些對迭代器有些奇怪的約束。例如,Java 的 Iterator 只能單向移動。這個 Iterator 只能用來:

1、使用 iterator() 方法要求集合返回一個 Iterator。 Iterator 將準備好返回序列中的第一個元素。
2、使用 next() 方法獲得序列中的下一個元素。
3、使用 hasNext() 方法檢查序列中是否還有元素。
4、使用 remove() 方法將迭代器最近返回的那個元素刪除。

// collections/SimpleIteration.java
import typeinfo.pets.*;
import java.util.*;

public class SimpleIteration {
  public static void main(String[] args) {
    List<Pet> pets = Pets.list(12);
    Iterator<Pet> it = pets.iterator();
    while(it.hasNext()) {
      Pet p = it.next();
      System.out.print(p.id() + ":" + p + " ");
    }
    System.out.println();
    // A simpler approach, when possible:
    for(Pet p : pets)
      System.out.print(p.id() + ":" + p + " ");
    System.out.println();
    // An Iterator can also remove elements:
    it = pets.iterator();
    for(int i = 0; i < 6; i++) {
      it.next();
      it.remove();
    }
    System.out.println(pets);
  }
}
/* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug
7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug
7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
*/

ListIterator

ListIterator 是一個更強大的 Iterator 子型別,它只能由各種 List 類生成。 Iterator 只能向前移動,而 ListIterator 可以雙向移動。它可以生成迭代器在列表中指向位置的後一個和前一個元素的索引,並且可以使用 set() 方法替換它訪問過的最近一個元素。可以通過呼叫 listIterator() 方法來生成指向 List 開頭處的ListIterator ,還可以通過呼叫 listIterator(n) 建立一個一開始就指向列表索引號為 n 的元素處的 ListIterator 。

連結串列LinkedList

LinkedList 也像 ArrayList 一樣實現了基本的 List 介面,但它在 List 中間執行插入和刪除操作時比 ArrayList 更高效。然而,它在隨機訪問操作效率方面卻要遜色一些。

LinkedList 還添加了一些方法,使其可以被用作棧、佇列或雙端佇列(deque) 。在這些方法中,有些彼此之間可能只是名稱有些差異,或者只存在些許差異,以使得這些名字在特定用法的上下文環境中更加適用(特別是在 Queue 中)。例如:

1、getFirst() 和 element() 是相同的,它們都返回列表的頭部(第一個元素)而並不刪除它,如果 List 為空,則丟擲 NoSuchElementException 異常。 peek() 方法與這兩個方法只是稍有差異,它在列表為空時返回 null 。
2、removeFirst() 和 remove() 也是相同的,它們刪除並返回列表的頭部元素,並在列表為空時丟擲 NoSuchElementException 異常。 poll() 稍有差異,它在列表為空時返回 null 。
3、addFirst() 在列表的開頭插入一個元素。
4、offer() 與 add() 和 addLast() 相同。 它們都在列表的尾部(末尾)新增一個元素。
5、removeLast() 刪除並返回列表的最後一個元素。

棧Stack

堆疊是“後進先出”(LIFO)集合。它有時被稱為疊加棧(pushdown stack),因為最後“壓入”(push)棧的元素,第一個被“彈出”(pop)棧。經常用來類比棧的事物是帶有彈簧支架的自助餐廳托盤。最後裝入的托盤總是最先拿出來使用的。peek() 方法將返回棧頂元素,但並不將其從棧頂刪除,而 pop() 刪除並返回頂部元素。

集合Set

Set 不儲存重複的元素。 如果試圖將相同物件的多個例項新增到 Set 中,那麼它會阻止這種重複行為。 Set 最常見的用途是測試歸屬性,可以很輕鬆地詢問某個物件是否在一個 Set 中。因此,查詢通常是 Set 最重要的操作,因此通常會選擇 HashSet 實現,該實現針對快速查詢進行了優化。

// collections/SetOfInteger.java
import java.util.*;

public class SetOfInteger {
  public static void main(String[] args) {
    Random rand = new Random(47);
    Set<Integer> intset = new HashSet<>();
    for(int i = 0; i < 10000; i++)
      intset.add(rand.nextInt(30));
    System.out.println(intset);
  }
}
/* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
*/

在 0 到 29 之間的 10000 個隨機整數被新增到 Set 中,因此可以想象每個值都重複了很多次。但是從結果中可以看到,每一個數只有一個例項出現在結果中。

對映Map

將物件對映到其他物件的能力是解決程式設計問題的有效方法。例如,考慮一個程式,它被用來檢查 Java 的 Random 類的隨機性。理想情況下, Random 會產生完美的數字分佈,但為了測試這一點,則需要生成大量的隨機數,並計算落在各種範圍內的數字個數。 Map 可以很容易地解決這個問題。在本例中,鍵是 Random 生成的數字,而值是該數字出現的次數:

// collections/Statistics.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Simple demonstration of HashMap
import java.util.*;

public class Statistics {
  public static void main(String[] args) {
    Random rand = new Random(47);
    Map<Integer, Integer> m = new HashMap<>();
    for(int i = 0; i < 10000; i++) {
      // Produce a number between 0 and 20:
      int r = rand.nextInt(20);
      Integer freq = m.get(r); // [1]
      m.put(r, freq == null ? 1 : freq + 1);
    }
    System.out.println(m);
  }
}
/* Output:
{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519,
7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506,
14=477, 15=497, 16=533, 17=509, 18=478, 19=464}
*/

[1] 自動包裝機制將隨機生成的 int 轉換為可以與 HashMap 一起使用的 Integer 引用(不能使用基本型別的集合)。如果鍵不在集合中,則 get() 返回 null (這意味著該數字第一次出現)。否則, get() 會為鍵生成與之關聯的 Integer 值,然後該值被遞增(自動包裝機制再次簡化了表示式,但實際上確實發生了對 Integer 的裝箱和拆箱)。

// collections/PetMap.java
import typeinfo.pets.*;
import java.util.*;

public class PetMap {
  public static void main(String[] args) {
    Map<String, Pet> petMap = new HashMap<>();
    petMap.put("My Cat", new Cat("Molly"));
    petMap.put("My Dog", new Dog("Ginger"));
    petMap.put("My Hamster", new Hamster("Bosco"));
    System.out.println(petMap);
    Pet dog = petMap.get("My Dog");
    System.out.println(dog);
    System.out.println(petMap.containsKey("My Dog"));
    System.out.println(petMap.containsValue(dog));
  }
}
/* Output:
{My Dog=Dog Ginger, My Cat=Cat Molly, My
Hamster=Hamster Bosco}
Dog Ginger
true
true
*/

Map 與陣列和其他的 Collection 一樣,可以輕鬆地擴充套件到多個維度,只需要建立一個值為 Map 的 Map(這些 Map 的值可以是其他集合,甚至是其他 Map)。因此,能夠很容易地將集合組合起來以快速生成強大的資料結構。例如,假設你正在追蹤有多個寵物的人,只需要一個 Map<Person, List> 即可。

佇列Queue

佇列是一個典型的“先進先出”(FIFO)集合。 即從集合的一端放入事物,再從另一端去獲取它們,事物放入集合的順序和被取出的順序是相同的。佇列通常被當做一種可靠的將物件從程式的某個區域傳輸到另一個區域的途徑。佇列在併發程式設計中尤為重要,因為它們可以安全地將物件從一個任務傳輸到另一個任務。

LinkedList 實現了 Queue 介面,並且提供了一些方法以支援佇列行為,因此 LinkedList 可以用作 Queue 的一種實現。 通過將 LinkedList 向上轉換為 Queue ,下面的示例使用了在 Queue 介面中與 Queue 相關(Queue-specific)的方法:

// collections/QueueDemo.java
// Upcasting to a Queue from a LinkedList
import java.util.*;

public class QueueDemo {
  public static void printQ(Queue queue) {
    while(queue.peek() != null)
      System.out.print(queue.remove() + " ");
    System.out.println();
  }
  public static void main(String[] args) {
    Queue<Integer> queue = new LinkedList<>();
    Random rand = new Random(47);
    for(int i = 0; i < 10; i++)
      queue.offer(rand.nextInt(i + 10));
    printQ(queue);
    Queue<Character> qc = new LinkedList<>();
    for(char c : "Brontosaurus".toCharArray())
      qc.offer(c);
    printQ(qc);
  }
}
/* Output:
8 1 1 1 5 14 3 1 0 1
B r o n t o s a u r u s
*/

offer() 是與 Queue 相關的方法之一,它在允許的情況下,在佇列的尾部插入一個元素,或者返回 false 。 peek() 和 element() 都返回隊頭元素而不刪除它,但是如果佇列為空,則 element() 丟擲 NoSuchElementException ,而 peek() 返回 null 。 poll() 和 remove()* 都刪除並返回隊頭元素,但如果佇列為空,poll() 返回 null ,而 remove() 丟擲 NoSuchElementException 。

自動包裝機制會自動將 nextInt() 的 int 結果轉換為 queue 所需的 Integer 物件,並將 char c 轉換為 qc 所需的 Character 物件。 Queue 介面窄化了對 LinkedList 方法的訪問許可權,因此只有適當的方法才能使用,因此能夠訪問到的 LinkedList 的方法會變少(這裡實際上可以將 Queue 強制轉換回 LinkedList ,但至少我們不鼓勵這樣做)。

集合和迭代器

for-in和迭代器

Java 5 引入了一個名為 Iterable 的介面,該介面包含一個能夠生成 Iterator 的 iterator() 方法。for-in 使用此 Iterable 介面來遍歷序列。因此,如果建立了任何實現了 Iterable 的類,都可以將它用於 for-in 語句中:

// collections/IterableClass.java
// Anything Iterable works with for-in
import java.util.*;

public class IterableClass implements Iterable<String> {
  protected String[] words = ("And that is how " +
    "we know the Earth to be banana-shaped."
    ).split(" ");
  @Override
  public Iterator<String> iterator() {
    return new Iterator<String>() {
      private int index = 0;
      @Override
      public boolean hasNext() {
        return index < words.length;
      }
      @Override
      public String next() { return words[index++]; }
      @Override
      public void remove() { // Not implemented
        throw new UnsupportedOperationException();
      }
    };
  }
  public static void main(String[] args) {
    for(String s : new IterableClass())
      System.out.print(s + " ");
  }
}
/* Output:
And that is how we know the Earth to be banana-shaped.
*/

iterator() 返回的是實現了 Iterator 的匿名內部類的例項,該匿名內部類可以遍歷陣列中的每個單詞。在主方法中,可以看到 IterableClass 確實可以用於 for-in 語句。

小結

Java 提供了許多儲存物件的方法:

1、陣列將數字索引與物件相關聯。它儲存型別明確的物件,因此在查詢物件時不必對結果做型別轉換。它可以是多維的,可以儲存基本型別的資料。雖然可以在執行時建立陣列,但是一旦建立陣列,就無法更改陣列的大小。

2、Collection 儲存單一的元素,而 Map 包含相關聯的鍵值對。使用 Java 泛型,可以指定集合中儲存的物件的型別,因此不能將錯誤型別的物件放入集合中,並且在從集合中獲取元素時,不必進行型別轉換。各種 Collection 和各種 Map 都可以在你向其中新增更多的元素時,自動調整其尺寸大小。集合不能儲存基本型別,但自動裝箱機制會負責執行基本型別和集合中儲存的包裝型別之間的雙向轉換。

3、像陣列一樣, List 也將數字索引與物件相關聯,因此,陣列和 List 都是有序集合。

4、如果要執行大量的隨機訪問,則使用 ArrayList ,如果要經常從表中間插入或刪除元素,則應該使用 LinkedList 。

5、佇列和堆疊的行為是通過 LinkedList 提供的。

6、Map 是一種將物件(而非數字)與物件相關聯的設計。 HashMap 專為快速訪問而設計,而 TreeMap 保持鍵始終處於排序狀態,所以沒有 HashMap 快。 LinkedHashMap 按插入順序儲存其元素,但使用雜湊提供快速訪問的能力。

7、Set 不接受重複元素。 HashSet 提供最快的查詢速度,而 TreeSet 保持元素處於排序狀態。 LinkedHashSet 按插入順序儲存其元素,但使用雜湊提供快速訪問的能力。

8、不要在新程式碼中使用遺留類 Vector ,Hashtable 和 Stack 。

瀏覽一下Java集合的簡圖(不包含抽象類或遺留元件)會很有幫助。這裡僅包括在一般情況下會碰到的介面和類。(譯者注:下圖為原著PDF中的截圖,可能由於未知原因存在問題。這裡可參考譯者繪製版[^8])


可以看到,實際上只有四個基本的集合元件: Map , List , Set 和 Queue ,它們各有兩到三個實現版本(Queue 的 java.util.concurrent 實現未包含在此圖中)。最常使用的集合用黑色粗線線框表示。

虛線框表示介面,實線框表示普通的(具體的)類。帶有空心箭頭的虛線表示特定的類實現了一個介面。實心箭頭表示某個類可以生成箭頭指向的類的物件。例如,任何 Collection 都可以生成 Iterator , List 可以生成 ListIterator (也能生成普通的 Iterator ,因為 List 繼承自 Collection )。